<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Richard Si – Assorted ramblings</title><link>https://sichard.ca/blog/</link><description>Recent content in Assorted ramblings on Richard Si</description><generator>Hugo -- gohugo.io</generator><language>en-ca</language><lastBuildDate>Sun, 26 Apr 2026 00:00:00 -0400</lastBuildDate><atom:link href="https://sichard.ca/blog/index.xml" rel="self" type="application/rss+xml"/><item><title>What's new in pip 26.1 - lockfiles and dependency cooldowns!</title><link>https://sichard.ca/blog/2026/04/whats-new-in-pip-26.1/</link><pubDate>Sun, 26 Apr 2026 00:00:00 -0400</pubDate><guid>https://sichard.ca/blog/2026/04/whats-new-in-pip-26.1/</guid><description>
&lt;p&gt;On April 26, 2026, we, the pip team, released pip 26.1.&lt;/p&gt;
&lt;p&gt;As always, please &lt;a href="https://pip.pypa.io/en/latest/news/#v26-1"target="_blank" rel="noopener"&gt;consult the changelog&lt;/a&gt; to learn about all of the changes
contained in this release.&lt;/p&gt;
&lt;h2&gt;Python 3.9 is no longer supported&lt;span class="hx:absolute hx:-mt-20" id="python-39-is-no-longer-supported"&gt;&lt;/span&gt;
&lt;a href="#python-39-is-no-longer-supported" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;pip 26.1 now targets Python 3.10 and higher.&lt;/p&gt;
&lt;p&gt;Python 3.9 entered End-of-Life (EOL) status six months ago in October 2025. At this point
in the year, many of pip&amp;rsquo;s vendored dependencies have stopped supporting Python 3.9, so
it made sense for pip to drop support as well.&lt;/p&gt;
&lt;h2&gt;New features ✨&lt;span class="hx:absolute hx:-mt-20" id="new-features-"&gt;&lt;/span&gt;
&lt;a href="#new-features-" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3&gt;Experimental: installing from pylock files&lt;span class="hx:absolute hx:-mt-20" id="experimental-installing-from-pylock-files"&gt;&lt;/span&gt;
&lt;a href="#experimental-installing-from-pylock-files" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;After a year since the acceptance of &lt;a href="https://peps.python.org/pep-0751/"target="_blank" rel="noopener"&gt;PEP 751&lt;/a&gt; which standardized &lt;code&gt;pylock.toml&lt;/code&gt; lock files,
pip 26.1 gains &lt;strong&gt;experimental&lt;/strong&gt; (&lt;a href="#whats-next"&gt;see below for more details on &amp;ldquo;experimental&amp;rdquo;&lt;/a&gt;)
support for reading and installing from such lockfiles. 🎉&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;-r &amp;lt;file&amp;gt;&lt;/code&gt;, &lt;code&gt;--requirement &amp;lt;file&amp;gt;&lt;/code&gt; option now accepts &lt;code&gt;pylock.toml&lt;/code&gt; files. You may pass a local
lockfile or a remote lockfile as long it is named as expected: &lt;code&gt;pylock.toml&lt;/code&gt; or &lt;code&gt;pylock.&amp;lt;name&amp;gt;.toml&lt;/code&gt;.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip install -r pylock.toml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;WARNING: Using pylock.toml as a requirements source is an experimental feature. It may be removed/changed in a future release without prior warning.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Collecting six==1.17.0 (from pylock.toml)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Downloading six-1.17.0-py2.py3-none-any.whl (11 kB)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Collecting urllib3==2.6.3 (from pylock.toml)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Downloading urllib3-2.6.3-py3-none-any.whl (131 kB)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Installing collected packages: urllib3, six
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Successfully installed six-1.17.0 urllib3-2.6.3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;pip install&lt;/code&gt;, &lt;code&gt;pip download&lt;/code&gt;, and &lt;code&gt;pip wheel&lt;/code&gt; all support reading lockfiles since
they accept &lt;code&gt;-r&lt;/code&gt;.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip wheel -r pylock.toml -w ./wheelhouse
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;WARNING: Using pylock.toml as a requirements source is an experimental feature. It may be removed/changed in a future release without prior warning.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Collecting six==1.17.0 (from pylock.toml)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Using cached six-1.17.0-py2.py3-none-any.whl (11 kB)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Collecting urllib3==2.6.3 (from pylock.toml)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Using cached urllib3-2.6.3-py3-none-any.whl (131 kB)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Saved ./wheelhouse/six-1.17.0-py2.py3-none-any.whl
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Saved ./wheelhouse/urllib3-2.6.3-py3-none-any.whl
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip install wheelhouse/*.whl --no-deps --no-index
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Processing ./wheelhouse/six-1.17.0-py2.py3-none-any.whl
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Processing ./wheelhouse/urllib3-2.6.3-py3-none-any.whl
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Installing collected packages: urllib3, six
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Successfully installed six-1.17.0 urllib3-2.6.3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;The intention of this feature is support &amp;ldquo;environment replication&amp;rdquo; from a lockfile.&lt;/strong&gt; Ideally,
&lt;code&gt;pip install -r pylock.toml&lt;/code&gt; is executed within an &lt;em&gt;empty&lt;/em&gt; environment, although this isn&amp;rsquo;t
required. While we are interested in hearing about additional use-cases, the pip team reserves
to declare such use-cases as out of scope.&lt;/p&gt;
&lt;p&gt;While you may mix &lt;code&gt;-r pylock.toml&lt;/code&gt; with any other normal requirement or constraint
specifier or file, as long as the total set of requested packages do not conflict, &lt;strong&gt;this
is strongly discouraged.&lt;/strong&gt; This is a side-effect of the current implementation, and it is
likely to change as the pip project stabilizes its lockfile support.&lt;/p&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:flex-col hx:rounded-lg hx:border hx:py-4 hx:px-4 hx:border-gray-200 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-blue-200 hx:bg-blue-100 hx:text-blue-900 hx:dark:border-blue-200/30 hx:dark:bg-blue-900/30 hx:dark:text-blue-200"&gt;
&lt;p class="hx:flex hx:items-center hx:font-medium"&gt;&lt;svg height=16px class="hx:inline-block hx:align-middle hx:mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/&gt;&lt;/svg&gt;Limitations and gotchas&lt;/p&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;&lt;ul&gt;
&lt;li&gt;Extras and dependency groups currently cannot be selected from a (multi-use) pylock
file&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pypa/pip/issues/13944"target="_blank" rel="noopener"&gt;VCS&lt;/a&gt; and &lt;a href="https://github.com/pypa/pip/issues/13943"target="_blank" rel="noopener"&gt;local directory&lt;/a&gt; entries currently cannot be mixed
with external requirements that are hash-locked&lt;/li&gt;
&lt;li&gt;The optional fallback to using &lt;code&gt;packages.index&lt;/code&gt; to resolve broken URLs is unsupported&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;By default, pip will let requirements from lock files pull in additional non-locked
requirements.&lt;/strong&gt; Pass &lt;code&gt;--no-deps&lt;/code&gt; if this is undesirable.&lt;/li&gt;
&lt;li&gt;Archive file sizes are currently not validated&lt;/li&gt;
&lt;li&gt;Only the format control options &lt;code&gt;--only-binary&lt;/code&gt; and &lt;code&gt;--no-binary&lt;/code&gt; will influence
locked requirements. All other package selection options (such as &lt;code&gt;--pre&lt;/code&gt;, &lt;code&gt;--abi&lt;/code&gt;,
etc.) will be ignored&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Please note that requirements sourced from a lockfile are treated specially. They
function like pinned requirements where the used archive or path is pre-constrained.&lt;/p&gt;
&lt;p&gt;If hashes are available for a locked requirement, pip will validate hashes as expected.&lt;/p&gt;
&lt;h4&gt;What&amp;rsquo;s next?&lt;span class="hx:absolute hx:-mt-20" id="whats-next"&gt;&lt;/span&gt;
&lt;a href="#whats-next" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;As pip&amp;rsquo;s lockfile support is experimental, &lt;code&gt;pip install -r pylock.toml&lt;/code&gt; and &lt;code&gt;pip lock&lt;/code&gt; should
&lt;em&gt;not&lt;/em&gt; be considered production ready. In the next few releases, the pip team will refine the
lockfile UI/UX in response to real-world experience and feedback.&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:flex-col hx:rounded-lg hx:border hx:py-4 hx:px-4 hx:border-gray-200 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-amber-200 hx:bg-amber-100 hx:text-amber-900 hx:dark:border-amber-200/30 hx:dark:bg-amber-900/30 hx:dark:text-amber-200"&gt;
&lt;p class="hx:flex hx:items-center hx:font-medium"&gt;&lt;svg height=16px class="hx:inline-block hx:align-middle hx:mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/&gt;&lt;/svg&gt;Warning&lt;/p&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;&lt;p&gt;We reserve the right to &lt;strong&gt;modify, deprecate, or remove experimental features without
notice&lt;/strong&gt;. pip&amp;rsquo;s support for reading pylock files via &lt;code&gt;-r pylock.toml&lt;/code&gt; is intended as a
transitional mechanism until pip can adopt expanded and better UI/UX.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We will be also working towards adding a &lt;code&gt;pip sync&lt;/code&gt; command which will function similar
to &lt;code&gt;pip-sync&lt;/code&gt; and &lt;code&gt;uv sync&lt;/code&gt; although in a way that makes sense for pip&amp;rsquo;s operating model.
We intend for &lt;code&gt;pip sync&lt;/code&gt; to be primary way in which pip interacts with lockfiles once
available.&lt;/p&gt;
&lt;p&gt;Thank you to Stéphane Bidoul who not only championed pip&amp;rsquo;s implementation, but upstreamed
pylock.toml support into the &lt;code&gt;packaging&lt;/code&gt; library so the whole Python ecosystem can benefit.&lt;/p&gt;
&lt;h4&gt;We need your feedback!&lt;span class="hx:absolute hx:-mt-20" id="we-need-your-feedback"&gt;&lt;/span&gt;
&lt;a href="#we-need-your-feedback" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;In the meanwhile, if you can, please test out &lt;code&gt;-r pylock.toml&lt;/code&gt;. If you have any feedback,
let us know in the following issues!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/pypa/pip/issues/13952"target="_blank" rel="noopener"&gt;&amp;ldquo;What&amp;rsquo;s next for &lt;code&gt;-r pylock.toml&lt;/code&gt;?&amp;rdquo;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pypa/pip/issues/13953"target="_blank" rel="noopener"&gt;&amp;ldquo;What&amp;rsquo;s next for &lt;code&gt;pip lock&lt;/code&gt;?&amp;rdquo;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pypa/pip/issues/13737"target="_blank" rel="noopener"&gt;&amp;ldquo;First party &lt;code&gt;pip sync&lt;/code&gt; command&amp;rdquo;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We&amp;rsquo;d especially appreciate comments on your use-case for lockfiles and how pip enables or
does not enable your use-case.&lt;/p&gt;
&lt;h3&gt;Dependency cooldowns&lt;span class="hx:absolute hx:-mt-20" id="dependency-cooldowns"&gt;&lt;/span&gt;
&lt;a href="#dependency-cooldowns" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;--uploaded-prior-to&lt;/code&gt; now supports accepts a relative duration in the &lt;code&gt;PnD&lt;/code&gt; format, where &lt;code&gt;n&lt;/code&gt;
is the number of days.&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt; This is meant to enable dependency cooldowns, which aim to reduce the
impact of compromised upstream packages.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip install --uploaded-prior-to&lt;span class="o"&gt;=&lt;/span&gt;P3D pip --force-reinstall &lt;span class="c1"&gt;# exclude the recent pip release&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Collecting pip
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Downloading pip-26.0.1-py3-none-any.whl.metadata (4.7 kB)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Downloading pip-26.0.1-py3-none-any.whl (1.8 MB)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.8/1.8 MB 2.3 MB/s 0:00:00
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Installing collected packages: pip
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Attempting uninstall: pip
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Found existing installation: pip 26.1.dev0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Uninstalling pip-26.1.dev0:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Successfully uninstalled pip-26.1.dev0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Successfully installed pip-26.0.1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;A dependency cooldown involves configuring a set period of time before a newly released package
is eligible for installation, providing time for package registries and third-party security firms
to discover and package authors to recover from a compromise.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s worth noting that while a cooldown can minimize the impact of supply chain attacks,
it will also delay security fixes from reaching your environment. If you use this option, pair it
with a vulnerability scanning tool such as Dependabot or pip-audit so that you are notified
of security issues independently of your update schedule.&lt;/p&gt;
&lt;p&gt;For more information, please consult William Woodruff&amp;rsquo;s excellent post
&lt;a href="https://blog.yossarian.net/2025/11/21/We-should-all-be-using-dependency-cooldowns"target="_blank" rel="noopener"&gt;&amp;ldquo;We should all be using dependency cooldowns&amp;rdquo;&lt;/a&gt;. Thank you also to Norbert Manthey
for contributing this feature to pip!&lt;/p&gt;
&lt;h3&gt;Lifting several 2020 resolver limitations&lt;span class="hx:absolute hx:-mt-20" id="lifting-several-2020-resolver-limitations"&gt;&lt;/span&gt;
&lt;a href="#lifting-several-2020-resolver-limitations" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;The modern pip resolver (&amp;ldquo;the 2020 resolver&amp;rdquo;) is miles better than the legacy resolver, capable
of resolving complex dependency trees without producing an invalid environment. However, pip has
kept its legacy resolver available as a deprecated feature via &lt;code&gt;--use-deprecated=legacy-resolver&lt;/code&gt;
to support use-cases that are unsupported by the 2020 resolver.&lt;/p&gt;
&lt;p&gt;Starting in pip 26.1, the 2020 resolver now supports most of these previously unsupported use-cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fix a crash when installing a requirement with an extra that has a corresponding URL constraint&lt;/li&gt;
&lt;li&gt;Properly apply URL constraints to requirements with extras&lt;/li&gt;
&lt;li&gt;Allow unpinned requirements to use hashes from constraints. Constraints
like &lt;code&gt;{name}=={version} --hash=...&lt;/code&gt; feeds into hash verification for
a corresponding requirement.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With a few remaining planned changes, pip will be in a position to fully remove the legacy
resolver, &lt;a href="https://github.com/pypa/pip/issues/10946"target="_blank" rel="noopener"&gt;which we would like to do sometime in 2027&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Security fixes&lt;span class="hx:absolute hx:-mt-20" id="security-fixes"&gt;&lt;/span&gt;
&lt;a href="#security-fixes" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3&gt;Avoid mistreating a .tar.gz archive as a zip file (CVE-2026-3219)&lt;span class="hx:absolute hx:-mt-20" id="avoid-mistreating-a-targz-archive-as-a-zip-file-cve-2026-3219"&gt;&lt;/span&gt;
&lt;a href="#avoid-mistreating-a-targz-archive-as-a-zip-file-cve-2026-3219" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;It&amp;rsquo;s possible for a &lt;code&gt;.tar.gz&lt;/code&gt; archive to look like a zip file, i.e. when given to the stdlib
&lt;code&gt;zipfile.is_zipfile()&lt;/code&gt; function, it will return true. On pip 26.0 and older, pip will ignore the
&lt;code&gt;.tar.gz&lt;/code&gt; contents and use the zip contents instead. This can be abused to easily obfuscate
malicious code.&lt;/p&gt;
&lt;p&gt;pip 26.1 updates the logic used to disambiguate tar and zip archives. pip will now use the
following signals, in decreasing priority:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Content type returned by the remote server&lt;/li&gt;
&lt;li&gt;File extension&lt;/li&gt;
&lt;li&gt;Python standard library magic detection functions &lt;strong&gt;if only&lt;/strong&gt; they unambiguously agree&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thank you to Dmitrii Sutiagin for contributing a fix and to Caleb Brown for reporting this
issue responsibly to the pip team.&lt;/p&gt;
&lt;h3&gt;Fix ACE caused by self check deferred imports (CVE-2026-6357)&lt;span class="hx:absolute hx:-mt-20" id="fix-ace-caused-by-self-check-deferred-imports-cve-2026-6357"&gt;&lt;/span&gt;
&lt;a href="#fix-ace-caused-by-self-check-deferred-imports-cve-2026-6357" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;pip will issue a notice if there is a newer version of pip available. For performance reasons
this check occurs at the end of a command invocation, with the responsible code being imported
when needed. If pip&amp;rsquo;s own modules are overwritten during a &lt;code&gt;pip install&lt;/code&gt;, this may result in
arbitrary code execution.&lt;/p&gt;
&lt;p&gt;pip 26.1 fixes this vulnerability by performing the self check eagerly before any command runs,
deferring only the emitting of the notice to the very end.&lt;/p&gt;
&lt;p&gt;Thank you to Damian Shaw for discovering and fixing this issue.&lt;/p&gt;
&lt;h3&gt;Upgrade to urllib3 2.x&lt;span class="hx:absolute hx:-mt-20" id="upgrade-to-urllib3-2x"&gt;&lt;/span&gt;
&lt;a href="#upgrade-to-urllib3-2x" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Now that pip requires Python 3.10, pip 26.1 ships with urllib3 2.6.3 instead of 1.26.20.
Unlike the 1.x series, 2.x is actively supported by the urllib3 project.&lt;/p&gt;
&lt;p&gt;This upgrade fixes a number of CVEs associated with the urllib3 library:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2026-21441"target="_blank" rel="noopener"&gt;CVE-2026-21441&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2025-66471"target="_blank" rel="noopener"&gt;CVE-2025-66471&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2025-50182"target="_blank" rel="noopener"&gt;CVE-2025-50182&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Please note that we consider these CVEs to be largely inconsequential for pip. The main
benefit for end-users is that any downstream security scanners will stop complaining
about pip&amp;rsquo;s vendored copy of urllib3.&lt;/p&gt;
&lt;h2&gt;Deprecations &amp;amp; upcoming removals&lt;span class="hx:absolute hx:-mt-20" id="deprecations--upcoming-removals"&gt;&lt;/span&gt;
&lt;a href="#deprecations--upcoming-removals" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Here&amp;rsquo;s the current list of current deprecations with the release in which they are
scheduled for removal. As always, any given removal may be &lt;strong&gt;pushed to a future release as
needed&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="pip-deprecation"&gt;
&lt;div class="header"&gt;
&lt;div class="title"&gt;&lt;code&gt;PIP_CONSTRAINT&lt;/code&gt; for build dependencies&lt;/div&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;div class="date"&gt;To be removed in pip 26.2&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;
&lt;p&gt;The &lt;code&gt;PIP_CONSTRAINT&lt;/code&gt; envvar will eventually stop taking effect for build dependencies.
We&amp;rsquo;re making this change to accommodate the planned transition to installing build
dependencies in-process. Affected users should use &lt;code&gt;--build-constraint&lt;/code&gt; or
&lt;code&gt;PIP_BUILD_CONSTRAINT&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;Acknowledgements&lt;span class="hx:absolute hx:-mt-20" id="acknowledgements"&gt;&lt;/span&gt;
&lt;a href="#acknowledgements" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;None so far. I did write this on a compressed timeline, however, so please forgive any typos.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;code&gt;--group&lt;/code&gt; will not do what you want. It will read from the local &lt;code&gt;pyproject.toml&lt;/code&gt;.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;If you take a look at the linked issues, there many open design questions left to
resolved. I have my own opinion and the other pip committers may have different
opinions. We want to get the UI/UX right, so please forgive us as we cycle on pip&amp;rsquo;s
lockfile support.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;In general, we don&amp;rsquo;t want pip to be an &lt;em&gt;innovator&lt;/em&gt; when it comes to new features
or new UX flows. We will aim to match pre-existing features from other package
managers where feasible.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;We chose to only support the simple &lt;code&gt;PnD&lt;/code&gt; ISO 8601 duration format to keep the parsing
code as minimal as feasible. We did not want to vendor a human time parsing library
or roll our own.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>What's new in pip 26.0 - prerelease and upload-time filtering!</title><link>https://sichard.ca/blog/2026/01/whats-new-in-pip-26.0/</link><pubDate>Fri, 30 Jan 2026 00:00:00 -0500</pubDate><guid>https://sichard.ca/blog/2026/01/whats-new-in-pip-26.0/</guid><description>
&lt;p&gt;On January 30, 2026, we, the pip team, released pip 26.0.&lt;/p&gt;
&lt;p&gt;As always, please &lt;a href="https://pip.pypa.io/en/latest/news/#v25-2"target="_blank" rel="noopener"&gt;consult the changelog&lt;/a&gt; to learn about all of the changes
contained in this release.&lt;/p&gt;
&lt;p&gt;Also, I apologise for the lack of a release post for pip 25.3. Changes have occurred in my
personal life that have resulted in me having very little free time. I was thus unable to find
the time to write a post in time for pip 25.3. Please enjoy this post for 26.0.&lt;/p&gt;
&lt;h2&gt;New features ✨&lt;span class="hx:absolute hx:-mt-20" id="new-features-"&gt;&lt;/span&gt;
&lt;a href="#new-features-" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3&gt;Excluding distributions by upload time&lt;span class="hx:absolute hx:-mt-20" id="excluding-distributions-by-upload-time"&gt;&lt;/span&gt;
&lt;a href="#excluding-distributions-by-upload-time" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;The new &lt;code&gt;--uploaded-prior-to&lt;/code&gt; option allows you to filter packages by their upload time to
a package index, only considering packages that were uploaded before a specified datetime.
This can be useful for debugging broken dependencies and ensuring reproducible builds by
ensuring you only install packages that were available at a known (good) point in time.&lt;/p&gt;
&lt;p&gt;A common situation is that you have a years old project (with no lockfile) and attempting
installing the dependencies results in a broken install. With &lt;code&gt;--uploaded-prior-to&lt;/code&gt;, you
can exclude packages uploaded after the last time you know the install worked and reobtain
the same set of working dependencies.&lt;/p&gt;
&lt;p&gt;The option accepts ISO 8601 datetime strings in several formats:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;2025-03-16&lt;/code&gt; - Date in implicit local timezone&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2025-03-16T12:30:00&lt;/code&gt; - Datetime in implict local timezone&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2025-03-16T12:30:00Z&lt;/code&gt; - Datetime in UTC&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2025-03-16T12:30:00+05:00&lt;/code&gt; - Datetime with local timezone offset&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For consistency, the timezone should be explicitly provided, although it is not required.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip install -r requirements.txt --uploaded-prior-to 2024-03-16T12:30:00+05:00
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[...]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Installing collected packages: platformdirs, pathspec, packaging, mypy-extensions, click, black
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Successfully installed black-24.3.0 click-8.1.7 mypy-extensions-1.0.0 packaging-24.0 pathspec-0.12.1 platformdirs-4.2.0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:flex-col hx:rounded-lg hx:border hx:py-4 hx:px-4 hx:border-gray-200 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-amber-200 hx:bg-amber-100 hx:text-amber-900 hx:dark:border-amber-200/30 hx:dark:bg-amber-900/30 hx:dark:text-amber-200"&gt;
&lt;p class="hx:flex hx:items-center hx:font-medium"&gt;&lt;svg height=16px class="hx:inline-block hx:align-middle hx:mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/&gt;&lt;/svg&gt;Warning: it is an exclusive upper bound!&lt;/p&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;&lt;p&gt;While similar to uv&amp;rsquo;s &lt;code&gt;--exclude-newer&lt;/code&gt; option,
&lt;code&gt;--uploaded-prior-to&lt;/code&gt; behaves a bit differently. Notably, &lt;code&gt;--uploaded-prior-to&lt;/code&gt; is an
exclusive upper bound for both datetimes and dates. In other words,
&lt;code&gt;--uploaded-prior-to 2025-01-01&lt;/code&gt; is equivalent to &lt;code&gt;--uploaded-prior-to 2025-01-01 00:00:00&lt;/code&gt;, not
&lt;code&gt;2025-01-01 23:59:59&lt;/code&gt; as with &lt;code&gt;--exclude-newer&lt;/code&gt;.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://pip.pypa.io/en/latest/user_guide/#filtering-by-upload-time"target="_blank" rel="noopener"&gt;More information can be found in the pip user guide&lt;/a&gt;. We thank uv for the
inspiration with their &lt;code&gt;--exclude-newer&lt;/code&gt; option.&lt;/p&gt;
&lt;h3&gt;Separate build constraints&lt;span class="hx:absolute hx:-mt-20" id="separate-build-constraints"&gt;&lt;/span&gt;
&lt;a href="#separate-build-constraints" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;This feature actually shipped in pip 25.3, but it&amp;rsquo;s noteworthy enough to mention in this
post.&lt;/p&gt;
&lt;p&gt;Build constraints can be set independently with &lt;code&gt;--build-constraint&lt;/code&gt;. This allows
constraining the versions of packages used during the build process (e.g., setuptools)
without affecting the final installation.&lt;/p&gt;
&lt;p&gt;This can be useful when a new release of a build backend is causing a package build to
error. You can constrain (block) the problematic versions of the backend and continue with
happily installing whatever you were installing earlier.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div class="hextra-code-filename not-prose" dir="auto"&gt;constraints.txt&lt;/div&gt;&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;setuptools&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mf"&gt;80.0.0&lt;/span&gt; &lt;span class="c1"&gt;# imagine this version broke your build&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-8"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip install myproject.tar.gz --build-constraint constraints.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Previously, your best option to constrain build dependencies was through setting the
&lt;code&gt;PIP_CONSTRAINT&lt;/code&gt; environment variable. Unlike the &lt;code&gt;--constraint&lt;/code&gt; option, the envvar would
be picked up by the pip subprocess invoked to install build dependencies. This works, but
is quite obscure and has the side-effect of constraining the final installation as well.&lt;/p&gt;
&lt;p&gt;At some point, we would like to stop calling pip in a subprocess to install build
dependencies and &lt;a href="https://github.com/pypa/pip/pull/13450"target="_blank" rel="noopener"&gt;switch to installing them in-process&lt;/a&gt;. This has a bunch
of benefits, but would break the &lt;code&gt;PIP_CONSTRAINT&lt;/code&gt; workaround. While we could update the
&lt;code&gt;--constraint&lt;/code&gt; option to also influence build dependencies , we are using this transition
as an opportunity to clean up constraint handling.&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;As part of this transition, pip has deprecated using &lt;code&gt;PIP_CONSTRAINT&lt;/code&gt; to constrain build
dependencies. Affected users are encouraged to use &lt;code&gt;--build-constraint&lt;/code&gt; or set
&lt;code&gt;PIP_BUILD_CONSTRAINT&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;--only-final&lt;/code&gt; and &lt;code&gt;--all-releases&lt;/code&gt;&lt;span class="hx:absolute hx:-mt-20" id="--only-final-and---all-releases"&gt;&lt;/span&gt;
&lt;a href="#--only-final-and---all-releases" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;By default, pip installs stable versions of packages, unless their specifier includes a
pre-release version (e.g., &lt;code&gt;SomePackage&amp;gt;=1.0a1&lt;/code&gt;) or if there are no stable&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; versions
available that satisfy the requirement. The &lt;code&gt;--all-releases&lt;/code&gt; and &lt;code&gt;--only-final&lt;/code&gt; options
provide &lt;strong&gt;per-package control&lt;/strong&gt; over pre-release selection.&lt;/p&gt;
&lt;p&gt;As their names imply, these new options allow you to tell pip to only consider stable or all
versions when resolving packages. They function like their &lt;code&gt;--no-binary&lt;/code&gt; and &lt;code&gt;--only-binary&lt;/code&gt;
cousins.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# accept the bleeding edge for a dependency&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m pip install --all-releases&lt;span class="o"&gt;=&lt;/span&gt;DependencyPackage SomePackage
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# we only accept stable versions for this dependency&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m pip install --only-final&lt;span class="o"&gt;=&lt;/span&gt;DependencyPackage SomePackage&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The pre-existing &lt;code&gt;--pre&lt;/code&gt; flag is now equivalent to &lt;code&gt;--all-releases :all:&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For more information, &lt;a href="https://pip.pypa.io/en/latest/user_guide/#controlling-pre-release-installation"target="_blank" rel="noopener"&gt;please consult the pip user guide&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Installing from inline script metadata (PEP 723)&lt;span class="hx:absolute hx:-mt-20" id="installing-from-inline-script-metadata-pep-723"&gt;&lt;/span&gt;
&lt;a href="#installing-from-inline-script-metadata-pep-723" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;This release has pip gain the &lt;code&gt;--requirements-from-script&lt;/code&gt; option for installing
dependencies declared in &lt;a href="https://packaging.python.org/en/latest/specifications/inline-script-metadata/#inline-script-metadata"target="_blank" rel="noopener"&gt;inline script metadata&lt;/a&gt; (PEP 723). Inline script metadata is
useful for scripts that depend on external libraries, but are designed to be distributed
as a single file.&lt;/p&gt;
&lt;p&gt;Recently, I had to write a script for converting a Jekyll site into a form that could be
imported by the &lt;a href="https://ghost.org"target="_blank" rel="noopener"&gt;Ghost CMS&lt;/a&gt;. Jekyll posts are stored as Markdown files with YAML
frontmatter, which meant I had to pull in PyYAML and a Markdown parser. This is no
problem. I can simply declare these dependencies using a &lt;code&gt;script&lt;/code&gt; metadata comment. No
separate &lt;code&gt;requirements.txt&lt;/code&gt; needed.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;Archive Jekyll posts into a JSON file Ghost can import.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# /// script&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# requires-python = &amp;#34;&amp;gt;=3.11&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# dependencies = [&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# &amp;#34;click&amp;#34;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# &amp;#34;rich&amp;#34;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# &amp;#34;python-frontmatter&amp;#34;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# &amp;#34;markdown-it-py[plugins]&amp;#34;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# &amp;#34;PyYaml&amp;#34;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ///&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;subprocess&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;zipfile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# and so on ...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;To run this script, I can simply run &lt;code&gt;pipx run ghost_export.py&lt;/code&gt;. pipx will see the script
comment and auto-install the dependencies before executing the script. This is extremely
convenient!&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:flex-col hx:rounded-lg hx:border hx:py-4 hx:px-4 hx:border-gray-200 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-blue-200 hx:bg-blue-100 hx:text-blue-900 hx:dark:border-blue-200/30 hx:dark:bg-blue-900/30 hx:dark:text-blue-200"&gt;
&lt;p class="hx:flex hx:items-center hx:font-medium"&gt;&lt;svg height=16px class="hx:inline-block hx:align-middle hx:mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/&gt;&lt;/svg&gt;Why a new option?&lt;/p&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;&lt;p&gt;This is a good time to explain why this feature is its own option.
While we could&amp;rsquo;ve overloaded the &lt;code&gt;-r&lt;/code&gt; option as suggested by some,
&lt;code&gt;--requirements-from-script&lt;/code&gt; is &lt;em&gt;not&lt;/em&gt; equivalent to &lt;code&gt;-r&lt;/code&gt;. If &lt;code&gt;requires-python&lt;/code&gt; is
declared in a script metadata block, pip will validate that the Python version is
compatible as well.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;And while we think that using pip with inline script metadata is suboptimal (you should
really be using a proper script runner), we recognise that there are situations where such
script runners are unavailable or undesirable.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;--requirements-from-script&lt;/code&gt; can also be given with &lt;code&gt;pip download&lt;/code&gt;, &lt;code&gt;pip lock&lt;/code&gt;, and
&lt;code&gt;pip wheel&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Thank you to &lt;a href="https://github.com/SnoopJ"target="_blank" rel="noopener"&gt;@SnoopJ&lt;/a&gt; for contributing this feature to pip!&lt;/p&gt;
&lt;h3&gt;Experimental: In-process build dependencies&lt;span class="hx:absolute hx:-mt-20" id="experimental-in-process-build-dependencies"&gt;&lt;/span&gt;
&lt;a href="#experimental-in-process-build-dependencies" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;The experimental feature to install build dependencies in-process, originally slated for
25.3, has now landed in pip 26.0. You should
&lt;a href="https://sichard.ca/blog/2025/07/whats-new-in-pip-25.2/#sneak-peek-in-process-build-dependencies"&gt;read my pip 25.2 post for the rational for this change&lt;/a&gt;,
but the benefits include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Faster build environment provisioning&lt;/li&gt;
&lt;li&gt;More reliable and less confusing option and flag inheritance&lt;/li&gt;
&lt;li&gt;In the future: support for authentication prompts while installing build dependencies&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It can be enabled with the &lt;code&gt;--use-feature=inprocess-build-deps&lt;/code&gt; flag, although the plan is
enable it by default in a future release once the feature stabilizes.&lt;/p&gt;
&lt;h2&gt;Deprecations &amp;amp; upcoming removals&lt;span class="hx:absolute hx:-mt-20" id="deprecations--upcoming-removals"&gt;&lt;/span&gt;
&lt;a href="#deprecations--upcoming-removals" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Here&amp;rsquo;s the current list of current deprecations with the release in which they are
scheduled for removal. As always, any given removal may be &lt;strong&gt;pushed to a future release as
needed&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="pip-deprecation"&gt;
&lt;div class="header"&gt;
&lt;div class="title"&gt;&lt;code&gt;PIP_CONSTRAINT&lt;/code&gt; for build dependencies&lt;/div&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;div class="date"&gt;To be removed in pip 26.2&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;
&lt;p&gt;The &lt;code&gt;PIP_CONSTRAINT&lt;/code&gt; envvar will eventually stop taking effect for build dependencies.
We&amp;rsquo;re making this change to accommodate the planned transition to installing build
dependencies in-process. Affected users should use &lt;code&gt;--build-constraint&lt;/code&gt; or
&lt;code&gt;PIP_BUILD_CONSTRAINT&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;Acknowledgements&lt;span class="hx:absolute hx:-mt-20" id="acknowledgements"&gt;&lt;/span&gt;
&lt;a href="#acknowledgements" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Thank you to Damian Shaw for reviewing and correcting some glaring mistakes. Any remaining
mistakes are my own.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;This also has the nice benefit of aligning our constraint options with uv which
already has &lt;code&gt;--build-constraint&lt;/code&gt;.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;What is a stable version? Any package whose version does NOT contain a development
segment or pre-release segment (&lt;code&gt;.alpha&lt;/code&gt;, &lt;code&gt;.beta&lt;/code&gt;, &lt;code&gt;.rc&lt;/code&gt;, &lt;code&gt;.dev&lt;/code&gt;).&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;You can use &lt;code&gt;uv run&lt;/code&gt; as well.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>What's new in pip 25.2 ... and a sneak peek of 25.3</title><link>https://sichard.ca/blog/2025/07/whats-new-in-pip-25.2/</link><pubDate>Wed, 30 Jul 2025 00:00:00 -0400</pubDate><guid>https://sichard.ca/blog/2025/07/whats-new-in-pip-25.2/</guid><description>
&lt;p&gt;On July 30, 2025, we, the pip team &lt;a href="https://discuss.python.org/t/announcement-pip-25-2-release/100716"target="_blank" rel="noopener"&gt;released pip 25.2&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Relative to the large 25.1 release, this release is tiny. It&amp;rsquo;s honestly so small that it
barely warrants a post, but it wouldn&amp;rsquo;t be a proper release without a post, so enjoy
whatever this is.&lt;/p&gt;
&lt;p&gt;As always, please &lt;a href="https://pip.pypa.io/en/latest/news/#v25-2"target="_blank" rel="noopener"&gt;consult the changelog&lt;/a&gt; to learn about all of the changes
contained in this release.&lt;/p&gt;
&lt;h2&gt;Key changes&lt;span class="hx:absolute hx:-mt-20" id="key-changes"&gt;&lt;/span&gt;
&lt;a href="#key-changes" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Python 3.14 is now supported, with fixes to file URL/path handling included.&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Automatic download resumption is now stable and enabled by default.🎉&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;By default, pip will resume a download up to five times. If more attempts are desired,
you may configure a higher limit with &lt;code&gt;--resume-retries&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In addition, caching support was added to resumed downloads so pip does not need to
redownload files as expected.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The download ETA is replaced by the elapsed time once the download has completed. While
&lt;code&gt;ETA 0:00:00&lt;/code&gt; is technically correct, it is not useful information 😉&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://sichard.ca/blog/2025/07/whats-new-in-pip-25.2/elapsed-time.png" alt="screeenshot of pip download numpy with a yellow elapsed time value near the progress bar" loading="lazy" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The truststore and proxy features can be used together without conflicting with each
other.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sub-commands are included in TAB completion.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://sichard.ca/blog/2025/07/whats-new-in-pip-25.2/sub-command-completion.png" alt="screeenshot of pip cache subcommands being offered as TAB completion options" loading="lazy" /&gt;&lt;/p&gt;
&lt;h2&gt;Deprecations &amp;amp; upcoming removals&lt;span class="hx:absolute hx:-mt-20" id="deprecations--upcoming-removals"&gt;&lt;/span&gt;
&lt;a href="#deprecations--upcoming-removals" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Here&amp;rsquo;s the current list of current deprecations with the release in which they are
scheduled for removal. As always, any given removal may be &lt;strong&gt;pushed to a future release as
needed&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="pip-deprecation"&gt;
&lt;div class="header"&gt;
&lt;div class="title"&gt;Non-bare project name in egg fragment&lt;/div&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;div class="date"&gt;To be removed in pip 25.3&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;
&lt;p&gt;&lt;a href="https://sichard.ca/blog/2025/01/whats-new-in-pip-25.0/#upcoming-removals"&gt;See the pip 25.0 post for more details.&lt;/a&gt; This got pushed to pip 25.3
as the support for the Direct URL syntax for installing editable VCS projects did not
land in this release, sadly.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="pip-deprecation"&gt;
&lt;div class="header"&gt;
&lt;div class="title"&gt;Legacy setup.py editable installs&lt;/div&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;div class="date"&gt;To be removed in pip 25.3&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;
&lt;p&gt;Please read &lt;a href="https://github.com/pypa/pip/issues/11457"target="_blank" rel="noopener"&gt;the deprecation issue for more details and advice&lt;/a&gt;. This was
scheduled for removal in 25.0 and then 25.1, but it got pushed back (again) to coincide
with the deprecation of &lt;code&gt;setup.py bdist_wheel&lt;/code&gt; installs.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="pip-deprecation"&gt;
&lt;div class="header"&gt;
&lt;div class="title"&gt;Legacy setup.py &lt;code&gt;bdist_wheel&lt;/code&gt; installs&lt;/div&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;div class="date"&gt;To be removed in pip 25.3&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;
&lt;p&gt;Please read &lt;a href="https://github.com/pypa/pip/issues/6334"target="_blank" rel="noopener"&gt;the deprecation issue for more details and advice&lt;/a&gt;, however,
the summary is that pip will stop running &lt;code&gt;setup.py bdist_wheel&lt;/code&gt; directly to build a
wheel for installation. This is a compatibility fallback for old environments that do
not support the modern PEP 517 interface.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="pip-deprecation"&gt;
&lt;div class="header"&gt;
&lt;div class="title"&gt;Non-standard wheel filenames&lt;/div&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;div class="date"&gt;To be removed in pip 25.3&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;
&lt;p&gt;This is a continuation of the old deprecation as the original deprecation did not catch
all non-standard wheel filenames.
&lt;a href="https://sichard.ca/blog/2025/01/whats-new-in-pip-25.0/#upcoming-removals"&gt;See the pip 25.0 post for more details.&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;Sneak peek: in-process build dependencies&lt;span class="hx:absolute hx:-mt-20" id="sneak-peek-in-process-build-dependencies"&gt;&lt;/span&gt;
&lt;a href="#sneak-peek-in-process-build-dependencies" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Since this release post is so small, I thought I&amp;rsquo;d take the time to share what I&amp;rsquo;ve been
working on in addition to my typical maintainer duties.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve ever installed a project from source, you&amp;rsquo;ve likely noticed that pip takes a
while to build the project.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; pip install ./dev/oss/pip
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Processing /home/ichard26/dev/oss/pip
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Installing build dependencies ... done
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Getting requirements to build wheel ... done
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Preparing metadata (pyproject.toml) ... done
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Building wheels for collected packages: pip
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Building wheel for pip (pyproject.toml) ... done
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Created wheel for pip: filename=pip-25.2.dev0-py3-none-any.whl size=1856601 sha256=8d02c1146bbeaa023421ac298240575fc2e96400ac3a7f86209426a8254dc0f8
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Stored in directory: /tmp-ram/pip-ephem-wheel-cache-4ctznmto/wheels/de/a6/21/441242ad27acc3bddc1079ff202cc70bad47d31432987f2bf0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Successfully built pip
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Installing collected packages: pip
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Successfully installed pip-25.2.dev0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;real 0m2.986s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;user 0m2.132s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sys 0m0.527s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;While pip is generally &lt;em&gt;not&lt;/em&gt; fast, &lt;strong&gt;PEP 517 builds&lt;/strong&gt;—which is the modern/default
mechanism for building projects—are particularly heavy. If I disable PEP 517 processing
and force pip to use the &lt;em&gt;legacy&lt;/em&gt; setuptools-specific build mechanism, the install is
noticeably faster.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; pip install ./dev/oss/pip --no-use-pep517
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[...]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;real 0m1.919s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;user 0m1.486s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sys 0m0.413s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The overhead of PEP 517 processing comes from two main sources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The PEP 517 interface itself.&lt;/strong&gt; To build a project in the PEP 517 way, pip must call
Python hooks provided by the configured build backend. For example, PEP 517 backends
must supply a &lt;code&gt;build_wheel&lt;/code&gt; function which when called builds the project and returns
the path to the resultant wheel. All of these calls are mandated to occur in fresh
subprocesses which each incur a Python startup/shutdown penalty. Yikes!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Build isolation.&lt;/strong&gt; In PEP 517 mode, pip by default sets up a temporary isolated
environment to perform the build in. The build backend and any additional build
dependencies are installed to this temporary environment on the fly.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In practice, pip has to call effectively the same hooks even under the legacy mechanism so
the design is not actually any slower.&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;strong&gt;The additional overhead is from build
isolation&lt;/strong&gt;. Creating the temporary environment is cheap, but installing build
dependencies is very much &lt;em&gt;not&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;A major part of the install time is spent on &lt;em&gt;accessing the network&lt;/em&gt;. Yes, pip is checking
PyPI (or the configured index) to determine the most up to date versions of build
dependencies every time it sets up a build environment. This is why building from source
often requires active Internet access with modern pip.&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;However, another major source of the overhead is from &lt;strong&gt;invoking a pip in a subprocess&lt;/strong&gt;
to install build dependencies. Large Python applications are rather slow to start up and
pip is no exception. For example, &lt;code&gt;pip install --help&lt;/code&gt; takes ~220ms to execute on my
system.&lt;/p&gt;
&lt;p&gt;Starting a new pip process also comes with other cons, including numerous bugs related to
configuration not being passed down and abysmal error reporting.&lt;/p&gt;
&lt;p&gt;Consequently,
&lt;a href="https://github.com/pypa/pip/issues/9081"target="_blank" rel="noopener"&gt;we&amp;rsquo;ve wanted to replace this mechanism and switch to installing build dependencies within the same root pip process for years&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The good news is that &lt;strong&gt;this work is finally starting to happen!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/pypa/pip/pull/13450"target="_blank" rel="noopener"&gt;I&amp;rsquo;ve published a draft PR&lt;/a&gt; that would add an alternative mechanism which
installs build dependencies in-process. There will be behavioural differences compared to
the current subprocess installation method, but those will be covered in a future post
when the feature is generally available for testing. The good news is that it is already
&lt;strong&gt;demonstrating performance improvements&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; pip install . --use-feature&lt;span class="o"&gt;=&lt;/span&gt;inprocess-build-deps
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[...]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;real 0m2.672s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;user 0m1.836s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sys 0m0.483s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;In comparison with the initial example, 300ms has been saved which is in line with the
startup overhead of the &lt;code&gt;pip install&lt;/code&gt; command.&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt; The configuration
inheritance story related to build dependencies will be cleaned up as part of this effort,
too.&lt;/p&gt;
&lt;p&gt;There remains considerable work (notably around error handling and adding more tests)
before the PR is ready for review, but if everything goes to plan, it &lt;em&gt;should&lt;/em&gt; be
&lt;strong&gt;available in pip 25.3 as an experimental feature&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Well, it &lt;em&gt;should&lt;/em&gt; be supported, anyway. There were &lt;em&gt;lots&lt;/em&gt; of test failures on Python
3.14 due to URL&amp;lt;-&amp;gt;path conversion issues. They were caused by a mix of upstream
changes in &lt;code&gt;pathname2url()&lt;/code&gt; and &lt;code&gt;url2pathname()&lt;/code&gt; and then undiscovered bugs in pip&amp;rsquo;s
URL&amp;lt;-&amp;gt;path utilities. It was hell, and frankly, I&amp;rsquo;m not 100% sure everything is right
as I am barely an expert on file URLs (even after reading RFCs for an hour).&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Although the design does serve to limit the lowest achievable build times even with
well-optimized tools like uv.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;&lt;a href="https://github.com/pypa/pip/issues/8057"target="_blank" rel="noopener"&gt;We recognize that it&amp;rsquo;d be nice if pip supported an offline mode&lt;/a&gt; where
pip fell back to using whatever packages were already stored in the HTTP or wheel
caches, but unfortunately, this would require a major refactoring of the caching
stack. If you&amp;rsquo;d like to help us out and suggest a proposal that&amp;rsquo;s feasible with the
limited resources the pip project has, you are more than welcome to do so.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;Remember that there is even more overhead beyond initial import times. A new pip
process also needs to configure the network stack (which can be quite slow on some
systems), load configuration, and shut down properly.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>What's new in pip 25.1 - Dependency groups!</title><link>https://sichard.ca/blog/2025/04/whats-new-in-pip-25.1/</link><pubDate>Fri, 25 Apr 2025 00:00:00 -0400</pubDate><guid>https://sichard.ca/blog/2025/04/whats-new-in-pip-25.1/</guid><description>
&lt;p&gt;On April 26, 2025, we, the pip team released pip 25.1.&lt;/p&gt;
&lt;p&gt;Compared to previous releases, this release is a large one. The &lt;a href="https://pip.pypa.io/en/latest/news/#v25-1"target="_blank" rel="noopener"&gt;changelog&lt;/a&gt; is quite long.
Among the changes, there are numerous new features, including an install progress bar and
support for &lt;a href="https://packaging.python.org/en/latest/specifications/dependency-groups/"target="_blank" rel="noopener"&gt;Dependency Groups&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;I won&amp;rsquo;t cover every single change here, so as always, please refer to the changelog for
the full list of changes.&lt;/p&gt;
&lt;h2&gt;Key features ✨&lt;span class="hx:absolute hx:-mt-20" id="key-features-"&gt;&lt;/span&gt;
&lt;a href="#key-features-" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3&gt;Dependency groups (PEP 735)&lt;span class="hx:absolute hx:-mt-20" id="dependency-groups-pep-735"&gt;&lt;/span&gt;
&lt;a href="#dependency-groups-pep-735" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Support for installing &lt;a href="https://packaging.python.org/en/latest/specifications/dependency-groups/"target="_blank" rel="noopener"&gt;Dependency Groups&lt;/a&gt; (PEP 735) is now available with the &lt;code&gt;--group&lt;/code&gt;
option.&lt;/p&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:flex-col hx:rounded-lg hx:border hx:py-4 hx:px-4 hx:border-gray-200 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-green-200 hx:bg-green-100 hx:text-green-900 hx:dark:border-green-200/30 hx:dark:bg-green-900/30 hx:dark:text-green-200"&gt;
&lt;p class="hx:flex hx:items-center hx:font-medium"&gt;&lt;svg height=16px class="hx:inline-block hx:align-middle hx:mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/&gt;&lt;/svg&gt;What are Dependency Groups?&lt;/p&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;&lt;p&gt;&lt;a href="https://packaging.python.org/en/latest/specifications/dependency-groups/"target="_blank" rel="noopener"&gt;Dependency Groups&lt;/a&gt; are the modern standardized replacement for requirements.txt files
and extras used for development workflows. Dependency Groups are &lt;strong&gt;NOT&lt;/strong&gt; published
nor installable by the end user, unlike extras.&lt;/p&gt;
&lt;p&gt;They are meant to replace files like &lt;code&gt;test-requirements.txt&lt;/code&gt; or extras like &lt;code&gt;[test]&lt;/code&gt;,
&lt;code&gt;[dev]&lt;/code&gt;, or &lt;code&gt;[doc]&lt;/code&gt;. They are defined in a &lt;code&gt;pyproject.toml&lt;/code&gt; file under the
&lt;code&gt;dependency-groups&lt;/code&gt; table.&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;First, let&amp;rsquo;s assume there is a &lt;code&gt;pyproject.toml&lt;/code&gt; file in the current directory that defines
&lt;code&gt;test&lt;/code&gt; and &lt;code&gt;lint&lt;/code&gt; dependency groups:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div class="hextra-code-filename not-prose" dir="auto"&gt;pyproject.toml&lt;/div&gt;&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;dependency-groups&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pytest&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pytest-xdist&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;lint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;mypy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;isort&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Dependency Groups can include other groups! ✨&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;dev&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="nx"&gt;include-group&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;test&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;include-group&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;lint&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-8"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;To install the &lt;code&gt;test&lt;/code&gt; group, one can simply pass &lt;code&gt;--group test&lt;/code&gt;.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip install --group &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You can repeat the option to install multiple groups. You can also use &lt;code&gt;--group&lt;/code&gt; with
&lt;code&gt;-r&lt;/code&gt;, &lt;code&gt;-e&lt;/code&gt;, and any other dependency specifier. Want to install the local project,
two groups, and a requirements.txt? You bet you can.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip install . --group lint --group &lt;span class="nb"&gt;test&lt;/span&gt; -r my-old-requirements.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;To install a dependency group from a &lt;code&gt;pyproject.toml&lt;/code&gt; file that exists in a different
directory, state the path before the group separated with a colon.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip install --group ./dev/vem/pyproject.toml:dev
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:flex-col hx:rounded-lg hx:border hx:py-4 hx:px-4 hx:border-gray-200 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-amber-200 hx:bg-amber-100 hx:text-amber-900 hx:dark:border-amber-200/30 hx:dark:bg-amber-900/30 hx:dark:text-amber-200"&gt;
&lt;p class="hx:flex hx:items-center hx:font-medium"&gt;&lt;svg height=16px class="hx:inline-block hx:align-middle hx:mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/&gt;&lt;/svg&gt;Warning&lt;/p&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;&lt;p&gt;pip will &lt;strong&gt;NOT&lt;/strong&gt; look in parent directories for a &lt;code&gt;pyproject.toml&lt;/code&gt; file to
install dependency groups from. pip does not have the concept of a project
workflow.&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Additionally, Dependency Groups are a standardized feature, thus pip-specific options
are &lt;strong&gt;NOT&lt;/strong&gt; supported. If you need to pass &lt;code&gt;--no-deps&lt;/code&gt; or some other pip flag, you
should continue to use a &lt;code&gt;requirements.txt&lt;/code&gt; file.&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Please read the &lt;a href="https://pip.pypa.io/en/latest/user_guide/#dependency-groups"target="_blank" rel="noopener"&gt;pip user guide for more information&lt;/a&gt;. You can also read
the motivation and rationale sections of &lt;a href="https://peps.python.org/pep-0735/"target="_blank" rel="noopener"&gt;PEP 735&lt;/a&gt; if you are curious about why this
feature came about.&lt;/p&gt;
&lt;p&gt;Thank you to &lt;a href="https://github.com/sirosen"target="_blank" rel="noopener"&gt;Stephen Rosen&lt;/a&gt; for writing and shepherding PEP 735 and contributing the pip
implementation. Congratulations! 🎉&lt;/p&gt;
&lt;h3&gt;Package installation progress bar&lt;span class="hx:absolute hx:-mt-20" id="package-installation-progress-bar"&gt;&lt;/span&gt;
&lt;a href="#package-installation-progress-bar" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;A progress bar has been added to &lt;code&gt;pip install&lt;/code&gt; that tracks the installation
step.&lt;/p&gt;
&lt;video controls loop&gt;
&lt;source src="install-progress.webm"&gt;
&lt;/video&gt;
&lt;p&gt;Previously, pip would provide no feedback on how many packages have been installed. Is pip
slowly installing a large package (e.g., torch) or is pip simply stuck? Who knows! The
only time you do know is when pip first needs to uninstall an existing package to install
a new version, emitting &lt;code&gt;Attempting uninstall: mypackage&lt;/code&gt; as it goes.&lt;/p&gt;
&lt;p&gt;Now, you can install every single &lt;a href="https://github.com/home-assistant/core/blob/dev/requirements_all.txt"target="_blank" rel="noopener"&gt;Home Assistant&lt;/a&gt; dependency and know much progress pip
has made after dependency resolution and downloads.&lt;/p&gt;
&lt;h3&gt;Resumable downloads&lt;span class="hx:absolute hx:-mt-20" id="resumable-downloads"&gt;&lt;/span&gt;
&lt;a href="#resumable-downloads" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Support for automatic download retrying is available as an experimental feature starting
with pip 25.1. The download retry limit can be configured using the &lt;code&gt;--resume-retries&lt;/code&gt;
option.&lt;/p&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:flex-col hx:rounded-lg hx:border hx:py-4 hx:px-4 hx:border-gray-200 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-purple-200 hx:bg-purple-100 hx:text-purple-900 hx:dark:border-purple-200/30 hx:dark:bg-purple-900/30 hx:dark:text-purple-200"&gt;
&lt;p class="hx:flex hx:items-center hx:font-medium"&gt;&lt;svg height=16px class="hx:inline-block hx:align-middle hx:mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/&gt;&lt;/svg&gt;Important&lt;/p&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;&lt;p&gt;Download retrying (&lt;code&gt;--resume-retries&lt;/code&gt;) is separate from &lt;em&gt;connection&lt;/em&gt; retrying
(&lt;code&gt;--retries&lt;/code&gt;).&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;When a download terminates early, pip will attempt to resume the download from where it
left off. If the remote server does not support download resumption, pip will fall back to
restarting the download. If the download is still incomplete after pip has run out of
retries, a diagnostic error will be raised.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://sichard.ca/blog/2025/04/whats-new-in-pip-25.1/incomplete-download-error.png" alt="screeenshot of pip diagnostic incomplete-download error" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;It has been a long-standing complaint that pip is unreliable on poor/flaky connections.
&lt;a href="https://github.com/pypa/pip/issues/4796"target="_blank" rel="noopener"&gt;Any downloads of a sufficiently large package would fail midway through&lt;/a&gt;,
forcing the user to retry the entire pip command, only for it to fail again. For users on
a slow or metered connection, pip is likely unusable. And if hash-checking was enabled,
then
&lt;a href="https://github.com/pypa/pip/issues/11153"target="_blank" rel="noopener"&gt;a confusing and misleading hash mismatch error would be raised&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you use the feature, &lt;strong&gt;please let us know how it goes&lt;/strong&gt;! We want to make sure it works
well. Assuming nothing is fundamentally broken, &lt;code&gt;--resume-retries&lt;/code&gt; will be changed in the
next release to allow a few download retries by default.&lt;/p&gt;
&lt;p&gt;Thank you to &lt;a href="https://github.com/gmargaritis"target="_blank" rel="noopener"&gt;George Margaritis&lt;/a&gt; for contributing this feature!&lt;/p&gt;
&lt;h3&gt;Experimental lockfile (PEP 751) generation: &lt;code&gt;pip lock&lt;/code&gt;&lt;span class="hx:absolute hx:-mt-20" id="experimental-lockfile-pep-751-generation-pip-lock"&gt;&lt;/span&gt;
&lt;a href="#experimental-lockfile-pep-751-generation-pip-lock" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;pylock.toml&lt;/code&gt; (&lt;a href="https://peps.python.org/pep-0751/"target="_blank" rel="noopener"&gt;PEP 751&lt;/a&gt;) is the recently accepted standard for Python lockfiles.
Lockfiles are alternatives to the classic &lt;code&gt;requirements.txt&lt;/code&gt; format, designed to enable
reproducible installation in a Python environment.&lt;/p&gt;
&lt;p&gt;pip 25.1 makes the first step at supporting the new lockfile format. &lt;a href="https://pip.pypa.io/en/latest/cli/pip_lock/"target="_blank" rel="noopener"&gt;The &lt;code&gt;pip lock&lt;/code&gt;
command&lt;/a&gt; has been added to create a &lt;code&gt;pylock.toml&lt;/code&gt; from a set of requirements.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example locking the pip project in editable mode and six.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip lock -e ./pip six --output - -qq
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;lock-version = &amp;#34;1.0&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;created-by = &amp;#34;pip&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[[packages]]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;name = &amp;#34;pip&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[packages.directory]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;path = &amp;#34;pip&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;editable = true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[[packages]]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;name = &amp;#34;six&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;version = &amp;#34;1.17.0&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[[packages.wheels]]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;name = &amp;#34;six-1.17.0-py2.py3-none-any.whl&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;url = &amp;#34;https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[packages.wheels.hashes]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sha256 = &amp;#34;4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The produced lockfile is specific to the Python version and platform pip is invoked under.
In other words, the lockfile is &lt;strong&gt;NOT&lt;/strong&gt; a universal lockfile.&lt;/p&gt;
&lt;p&gt;The command is experimental, meant to enable basic locked installation scenarios. It is
expected to undergo further development once the lockfile standard sees more widespread
adoption and matures.&lt;/p&gt;
&lt;p&gt;Installing &lt;em&gt;from&lt;/em&gt; a lockfile is &lt;strong&gt;unsupported&lt;/strong&gt;, but
&lt;a href="https://github.com/pypa/pip/issues/13334"target="_blank" rel="noopener"&gt;it is on the roadmap&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Congratulations to &lt;a href="https://github.com/brettcannon"target="_blank" rel="noopener"&gt;Brett Cannon&lt;/a&gt; for getting a lockfile standardized after many years(!) of
PEP drafting and several rounds of discussion. I look forward to seeing &lt;code&gt;pylock.toml&lt;/code&gt;
mature!&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;pip index versions&lt;/code&gt; is stable&lt;span class="hx:absolute hx:-mt-20" id="pip-index-versions-is-stable"&gt;&lt;/span&gt;
&lt;a href="#pip-index-versions-is-stable" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;pip index versions&lt;/code&gt; command is no longer experimental. It is the modern replacement
for the old &lt;code&gt;pip install mypackage==&lt;/code&gt; hack which used to be an
&lt;a href="https://github.com/pypa/pip/issues/12852"target="_blank" rel="noopener"&gt;easy way to get the list of all available versions&lt;/a&gt;.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip index versions six
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;six (1.17.0)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Available versions: 1.17.0, 1.16.0, 1.15.0, 1.14.0, 1.13.0, 1.12.0, 1.11.0, 1.10.0, 1.9.0, 1.8.0, 1.7.3, 1.7.2, 1.7.1, 1.7.0, 1.6.1, 1.6.0, 1.5.2, 1.5.1, 1.5.0, 1.4.1, 1.4.0, 1.3.0, 1.2.0, 1.1.0, 1.0.0, 0.9.2, 0.9.1, 0.9.0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; INSTALLED: 1.17.0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; LATEST: 1.17.0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;In addition, you can now ask for JSON output using the &lt;code&gt;--json&lt;/code&gt; flag.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip index versions six --json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;{&amp;#34;name&amp;#34;: &amp;#34;six&amp;#34;, &amp;#34;versions&amp;#34;: [&amp;#34;1.17.0&amp;#34;, &amp;#34;1.16.0&amp;#34;, &amp;#34;1.15.0&amp;#34;, &amp;#34;1.14.0&amp;#34;, &amp;#34;1.13.0&amp;#34;, &amp;#34;1.12.0&amp;#34;, &amp;#34;1.11.0&amp;#34;, &amp;#34;1.10.0&amp;#34;, &amp;#34;1.9.0&amp;#34;, &amp;#34;1.8.0&amp;#34;, &amp;#34;1.7.3&amp;#34;, &amp;#34;1.7.2&amp;#34;, &amp;#34;1.7.1&amp;#34;, &amp;#34;1.7.0&amp;#34;, &amp;#34;1.6.1&amp;#34;, &amp;#34;1.6.0&amp;#34;, &amp;#34;1.5.2&amp;#34;, &amp;#34;1.5.1&amp;#34;, &amp;#34;1.5.0&amp;#34;, &amp;#34;1.4.1&amp;#34;, &amp;#34;1.4.0&amp;#34;, &amp;#34;1.3.0&amp;#34;, &amp;#34;1.2.0&amp;#34;, &amp;#34;1.1.0&amp;#34;, &amp;#34;1.0.0&amp;#34;, &amp;#34;0.9.2&amp;#34;, &amp;#34;0.9.1&amp;#34;, &amp;#34;0.9.0&amp;#34;], &amp;#34;latest&amp;#34;: &amp;#34;1.17.0&amp;#34;, &amp;#34;installed_version&amp;#34;: &amp;#34;1.17.0&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;If you use &lt;code&gt;pip index versions&lt;/code&gt; programmatically, you are encouraged to switch to the
&lt;code&gt;--json&lt;/code&gt; output. Not only is it easier to parse, but you will avoid breakage if the
human-friendly format ever changes later.&lt;/p&gt;
&lt;p&gt;Thank you to &lt;a href="https://github.com/KrishanBhasin"target="_blank" rel="noopener"&gt;Krishan Bhasin&lt;/a&gt; for stabilizing &lt;code&gt;pip index versions&lt;/code&gt; and adding JSON support!&lt;/p&gt;
&lt;h2&gt;Key bugfixes&lt;span class="hx:absolute hx:-mt-20" id="key-bugfixes"&gt;&lt;/span&gt;
&lt;a href="#key-bugfixes" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3&gt;Dependency resolution improvements&lt;span class="hx:absolute hx:-mt-20" id="dependency-resolution-improvements"&gt;&lt;/span&gt;
&lt;a href="#dependency-resolution-improvements" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Dependency resolution is not my area of expertise, so I&amp;rsquo;m going to shamelessly steal the
changelog entries that &lt;a href="https://github.com/notatallshaw"target="_blank" rel="noopener"&gt;Damian Shaw&lt;/a&gt; wrote:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Speed up resolution by first &lt;em&gt;only&lt;/em&gt; considering the &amp;ldquo;priorities&amp;rdquo; of candidates that must
be required to complete the resolution.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Improved heuristics for determining the order of dependency resolution by preferring
direct URL candidates and requirements with upper bounds.&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The upgrade to ResolveLib 1.1.0 fixes a known issue where pip
&lt;a href="https://github.com/pypa/pip/issues/12317"target="_blank" rel="noopener"&gt;&lt;strong&gt;would report a &lt;code&gt;ResolutionImpossible&lt;/code&gt; error even though there is a valid solution&lt;/strong&gt;&lt;/a&gt;.
However, some very complex scenarios that previously resolved may resolve slower or fail
with an &lt;code&gt;ResolutionTooDeep&lt;/code&gt; error.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;ResolutionTooDeep&lt;/code&gt; error has been converted into a more user-friendly diagnostic
error. If you have any real-world examples that result in a &lt;code&gt;ResolutionTooDeep&lt;/code&gt; error,
&lt;a href="https://github.com/pypa/pip/issues/13281"target="_blank" rel="noopener"&gt;&lt;strong&gt;we would like to hear them&lt;/strong&gt;&lt;/a&gt;!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Legacy &lt;code&gt;.egg&lt;/code&gt; distributions are only detected once&lt;span class="hx:absolute hx:-mt-20" id="legacy-egg-distributions-are-only-detected-once"&gt;&lt;/span&gt;
&lt;a href="#legacy-egg-distributions-are-only-detected-once" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;There are several formats that an installed package can be recorded in. Today, virtually
every package is installed using the &lt;a href="https://packaging.python.org/en/latest/specifications/recording-installed-packages/"target="_blank" rel="noopener"&gt;&lt;code&gt;.dist-info&lt;/code&gt; format&lt;/a&gt;. However, legacy
&lt;a href="https://setuptools.pypa.io/en/latest/deprecated/python_eggs.html"target="_blank" rel="noopener"&gt;&lt;code&gt;.egg&lt;/code&gt; distributions&lt;/a&gt; are still kicking around.&lt;/p&gt;
&lt;p&gt;Support for &lt;code&gt;.egg&lt;/code&gt; distributions has been deprecated since pip 23.2. In pip 25.1, this
deprecation was finalized&amp;hellip; but wait, pip still supports &lt;code&gt;.egg&lt;/code&gt; distributions! It turns
out that &lt;a href="https://docs.python.org/3/library/importlib.metadata.html"target="_blank" rel="noopener"&gt;&lt;code&gt;importlib.metadata&lt;/code&gt;&lt;/a&gt;, the library used to discover installed packages on Python
3.11+, supports &lt;code&gt;.egg&lt;/code&gt; distributions just fine.&lt;/p&gt;
&lt;p&gt;This meant that &lt;code&gt;.egg&lt;/code&gt; distributions were being discovered several times. The
&lt;code&gt;importlib.metadata&lt;/code&gt; backend would find it, and then the &lt;a href="https://setuptools.pypa.io/en/latest/pkg_resources.html"target="_blank" rel="noopener"&gt;&lt;code&gt;pkg_resources&lt;/code&gt;&lt;/a&gt; backend,
leading to this confusing &lt;code&gt;pip list&lt;/code&gt; output.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip list
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Package Version
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;----------------------------- ---------------------
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;mypackage 0.7.16
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;mypackage 0.7.16
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;In pip 25.1, the &lt;code&gt;pkg_resources&lt;/code&gt; backend is no longer used to discover &lt;code&gt;.egg&lt;/code&gt;
distributions and this duplication will not occur. Warnings about the &lt;code&gt;.egg&lt;/code&gt; deprecation
will no longer be printed.&lt;/p&gt;
&lt;h2&gt;Deprecations &amp;amp; upcoming removals&lt;span class="hx:absolute hx:-mt-20" id="deprecations--upcoming-removals"&gt;&lt;/span&gt;
&lt;a href="#deprecations--upcoming-removals" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Starting with this release, the pip project no longer supports Python 3.8.&lt;/p&gt;
&lt;p&gt;In addition, here is a list of current deprecations and the release they will be removed.
As always, any given removal may be &lt;strong&gt;pushed to a future release as needed&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="pip-deprecation"&gt;
&lt;div class="header"&gt;
&lt;div class="title"&gt;Non-bare project name in egg fragment&lt;/div&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;div class="date"&gt;To be removed in pip 25.2&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;
&lt;p&gt;&lt;a href="https://sichard.ca/blog/2025/01/whats-new-in-pip-25.0/#upcoming-removals"&gt;See the pip 25.0 post for more details.&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="pip-deprecation"&gt;
&lt;div class="header"&gt;
&lt;div class="title"&gt;Legacy setup.py editable installs&lt;/div&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;div class="date"&gt;To be removed in pip 25.3&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;
&lt;p&gt;Please read &lt;a href="https://github.com/pypa/pip/issues/11457"target="_blank" rel="noopener"&gt;the deprecation issue for more details and advice&lt;/a&gt;. This was
scheduled for removal in 25.0 and then 25.1, but it got pushed back (again) to coincide
with the deprecation of &lt;code&gt;setup.py bdist_wheel&lt;/code&gt; installs.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="pip-deprecation"&gt;
&lt;div class="header"&gt;
&lt;div class="title"&gt;Legacy setup.py &lt;code&gt;bdist_wheel&lt;/code&gt; installs&lt;/div&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;div class="date"&gt;To be removed in pip 25.3&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;
&lt;p&gt;Please read &lt;a href="https://github.com/pypa/pip/issues/6334"target="_blank" rel="noopener"&gt;the deprecation issue for more details and advice&lt;/a&gt;, however,
the summary is that pip will stop running &lt;code&gt;setup.py bdist_wheel&lt;/code&gt; directly to build a
wheel for installation. This is a compatibility fallback for old environments that do
not support the modern PEP 517 interface.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="pip-deprecation"&gt;
&lt;div class="header"&gt;
&lt;div class="title"&gt;Non-standard wheel filenames&lt;/div&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;div class="date"&gt;To be removed in pip 25.3&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;
&lt;p&gt;This is a continuation of the old deprecation as the original deprecation did not catch
all non-standard wheel filenames.
&lt;a href="https://sichard.ca/blog/2025/01/whats-new-in-pip-25.0/#upcoming-removals"&gt;See the pip 25.0 post for more details.&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;Acknowledgements&lt;span class="hx:absolute hx:-mt-20" id="acknowledgements"&gt;&lt;/span&gt;
&lt;a href="#acknowledgements" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Thank you to Damian Shaw, Emma Smith, Hugo van Kemenade, and Stéphane Bidoul reviewing the
draft of this post. Any typos or glaring mistakes are my own.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;They are not defined under the &lt;code&gt;project&lt;/code&gt; table because Dependency Groups
are meant to be a general feature that work regardless of whether you have an actual Python
package to install or not.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;There has been extensive discussion about pip&amp;rsquo;s fundamental design
and whether it should change. There is too much to easily summarize, but broadly, pip
has always been designed as a package manager, and &lt;em&gt;not&lt;/em&gt; as a &lt;em&gt;project&lt;/em&gt; workflow tool.
There are many other tools that compete in the project management space. The consensus
during review is that pip is valuable as a foundational tool that simply manages your
installed Python packages and does what you want without too much fuss. More practically,
shifting to a project-based model would constitute a major redesign of the pip UI and UX.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;This is a limitation of the specification. However, if sufficient
consensus builds around certain tool-specific flags, they can be standardized so they
can be included in Dependency Groups in the future.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;Please don&amp;rsquo;t add upper bounds &lt;strong&gt;unless&lt;/strong&gt; you know for a fact your codebase is
incompatible with the newer versions. Pre-emptive upper-bounds will not help
dependency resolution and &lt;a href="https://iscinumpy.dev/post/bound-version-constraints/"target="_blank" rel="noopener"&gt;are generally considered harmful&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Speeding up pip's Windows CI by setting TEMP to the D: drive</title><link>https://sichard.ca/blog/2025/03/faster-pip-ci-on-windows-d-drive/</link><pubDate>Mon, 10 Mar 2025 00:00:00 -0400</pubDate><guid>https://sichard.ca/blog/2025/03/faster-pip-ci-on-windows-d-drive/</guid><description>
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:flex-col hx:rounded-lg hx:border hx:py-4 hx:px-4 hx:border-gray-200 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-green-200 hx:bg-green-100 hx:text-green-900 hx:dark:border-green-200/30 hx:dark:bg-green-900/30 hx:dark:text-green-200"&gt;
&lt;p class="hx:flex hx:items-center hx:font-medium"&gt;&lt;svg height=16px class="hx:inline-block hx:align-middle hx:mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/&gt;&lt;/svg&gt;TL;DR&lt;/p&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;&lt;p&gt;If your GHA Windows jobs are I/O heavy, double check whether the I/O work is happening
on the C: drive (OS) or D: drive (working space). If the C: drive is being used, you
will likely benefit by &lt;strong&gt;moving that work to the D: drive&lt;/strong&gt;. If you&amp;rsquo;re feeling more
adventurous, you should also &lt;strong&gt;consider trying a ReFS/Dev Drive&lt;/strong&gt;.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;A few weeks ago, I was waiting for pip&amp;rsquo;s CI to turn 🍏 so I could merge a PR. I&amp;rsquo;d just
spent an hour on review and had pushed a few final tweaks. Waiting an additional 20
minutes was the last thing I wanted to do.&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;I was sufficiently annoyed with checking and re-checking the CI status, so I enabled
&lt;a href="https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/automatically-merging-a-pull-request"target="_blank" rel="noopener"&gt;auto-merging on the repository&lt;/a&gt; so I could at least focus my attention on something
entirely different while waiting for CI to pass. That smoothed over the pain, but it got
me wondering, can pip&amp;rsquo;s tests (and CI in particular) be faster?&lt;/p&gt;
&lt;p&gt;It turns out for the Windows CI jobs, the answer was: &lt;strong&gt;yes, they could be &lt;em&gt;much&lt;/em&gt;
faster.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;How slow was pip&amp;rsquo;s Windows CI?&lt;span class="hx:absolute hx:-mt-20" id="how-slow-was-pips-windows-ci"&gt;&lt;/span&gt;
&lt;a href="#how-slow-was-pips-windows-ci" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;At the time of writing&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;, pip&amp;rsquo;s Github Actions CI workflow consists of 28 jobs. 23
run the test suite across Ubuntu, macOS, Windows, and CPython 3.8 through 3.13.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;7 on the &lt;code&gt;ubuntu-latest&lt;/code&gt; runners&lt;/li&gt;
&lt;li&gt;6 on the &lt;code&gt;macos-latest&lt;/code&gt; (ARM) runners&lt;/li&gt;
&lt;li&gt;6 on the &lt;code&gt;macos-13&lt;/code&gt; (Intel) runners&lt;/li&gt;
&lt;li&gt;4 on the &lt;code&gt;windows-latest&lt;/code&gt; runners&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The extra Ubuntu job runs the functional test suite against a &lt;a href="https://pip.pypa.io/en/stable/installation/#standalone-zip-application"target="_blank" rel="noopener"&gt;zipapp&lt;/a&gt; version of pip.&lt;/p&gt;
&lt;p&gt;On the other hand, the Windows jobs have historically been so slow that we don&amp;rsquo;t even test
pip across the entire supported Python version matrix. We only test the oldest and newest
version, 3.8 and 3.13, on Windows. Plus, the test suite is
&lt;a href="https://github.com/pypa/pip/blob/ffbf6f0ce61170d6437ad5ff3a90086200ba9e2a/.github/workflows/ci.yml#L183-L185"target="_blank" rel="noopener"&gt;sharded over two jobs per version&lt;/a&gt; because—as you can probably guess—the Windows jobs are
that much slower 🐌.&lt;/p&gt;
&lt;p&gt;I wrote a script that queries the GitHub API and calculates the average job duration over
the last X successful runs. As a baseline, this is what pip&amp;rsquo;s CI looked like:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Last 50 CI runs on main
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Job Mean Minimum Stdev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ─────────────────────────────────────────────────────────────
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tests / 3.10 / macos-latest 6:25 5:44 0:34 (8%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tests / 3.11 / macos-latest 6:40 6:03 0:39 (9%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tests / 3.9 / macos-latest 6:57 6:03 0:55 (13%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tests / 3.13 / macos-latest 6:59 6:15 0:37 (8%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tests / 3.12 / macos-latest 7:09 6:14 1:01 (14%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tests / 3.8 / macos-latest 7:11 6:32 0:31 (7%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tests / 3.11 / ubuntu-latest 9:33 9:08 0:10 (1%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tests / 3.10 / ubuntu-latest 10:04 9:43 0:14 (2%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tests / 3.12 / ubuntu-latest 10:34 10:16 0:14 (2%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tests / 3.13 / ubuntu-latest 10:35 10:10 0:12 (2%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; tests / 3.13 / Windows / 2 10:39 9:55 0:20 (3%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tests / 3.8 / ubuntu-latest 11:02 10:41 0:14 (2%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; tests / 3.13 / Windows / 1 11:13 10:29 0:24 (3%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tests / 3.9 / ubuntu-latest 11:44 11:20 0:19 (2%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tests / 3.10 / macos-13 12:10 8:53 2:16 (18%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tests / 3.11 / macos-13 12:59 9:09 2:34 (19%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tests / 3.13 / macos-13 12:59 9:54 2:41 (20%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tests / 3.9 / macos-13 13:10 10:09 2:42 (20%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tests / 3.8 / macos-13 13:34 10:09 2:30 (18%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tests / 3.12 / macos-13 13:55 9:54 3:23 (24%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; tests / 3.8 / Windows / 2 14:58 14:04 0:26 (2%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tests / zipapp 16:53 16:27 0:15 (1%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; tests / 3.8 / Windows / 1 17:37 16:40 0:30 (2%)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Among the slowest three jobs, the Windows 3.8 jobs hold the 3rd and 1st places for
duration. Yikes!&lt;/p&gt;
&lt;h3&gt;Windows file I/O is poor on GitHub Actions&lt;span class="hx:absolute hx:-mt-20" id="windows-file-io-is-poor-on-github-actions"&gt;&lt;/span&gt;
&lt;a href="#windows-file-io-is-poor-on-github-actions" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;a href="https://github.com/actions/runner-images/issues/8755"target="_blank" rel="noopener"&gt;&lt;strong&gt;It&amp;rsquo;s a known issue that file I/O on the Windows runners is noticeably slower&lt;/strong&gt;&lt;/a&gt;
than the Ubuntu and macOS runners.&lt;/p&gt;
&lt;p&gt;As a package installer, &lt;strong&gt;pip&amp;rsquo;s test suite is especially I/O heavy&lt;/strong&gt;. Most functional
tests require at least setting a scratch environment and installing something into it.
Countless files and directories are created, opened, and deleted during a run.&lt;/p&gt;
&lt;h2&gt;Moving TEMP to the D: drive&lt;span class="hx:absolute hx:-mt-20" id="moving-temp-to-the-d-drive"&gt;&lt;/span&gt;
&lt;a href="#moving-temp-to-the-d-drive" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://github.com/pypa/pip/pull/13123#issuecomment-2561079373"target="_blank" rel="noopener"&gt;According to Steve Dower&lt;/a&gt;, the GHA Windows workers have a &lt;strong&gt;slow&lt;/strong&gt; (but read-optimized)
&lt;strong&gt;C: system drive and a faster D: drive for working space&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:flex-col hx:rounded-lg hx:border hx:py-4 hx:px-4 hx:border-gray-200 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-amber-200 hx:bg-amber-100 hx:text-amber-900 hx:dark:border-amber-200/30 hx:dark:bg-amber-900/30 hx:dark:text-amber-200"&gt;
&lt;p class="hx:flex hx:items-center hx:font-medium"&gt;&lt;svg height=16px class="hx:inline-block hx:align-middle hx:mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/&gt;&lt;/svg&gt;Warning&lt;/p&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;&lt;p&gt;Only the standard Windows runners have a D: drive. The larger (paid) Window
runners do NOT have a D: drive. For such situations,
&lt;a href="#what-about-refs-or-dev-drives"&gt;ReFS/Dev Drives should be considered&lt;/a&gt;.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;For many projects, this is &lt;em&gt;probably&lt;/em&gt; fine. The repository is still checked out on the D:
drive. Any files you create in the checkout will be on the D: drive. For pip&amp;rsquo;s test suite,
however, all of the temporary files are created under the system temporary directory.
Where is this path set to by default? On the C: drive at
&lt;code&gt;C:/Users/runneradmin/AppData/Local/Temp/&lt;/code&gt;.&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Thus, pip&amp;rsquo;s CI was being slowed down by reading and writing primarily to the
non-performant disk.&lt;/p&gt;
&lt;p&gt;The good news is that this can be overridden by setting the &lt;code&gt;TEMP&lt;/code&gt; environment variable
which Python&amp;rsquo;s &lt;code&gt;tempfile&lt;/code&gt; will respect.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set TEMP to D:/Temp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; mkdir &amp;#34;D:\\Temp&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;TEMP=D:\\Temp&amp;#34; &amp;gt;&amp;gt; $env:GITHUB_ENV&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/pypa/pip/pull/13129"target="_blank" rel="noopener"&gt;This simple switch to &lt;code&gt;D:/Temp&lt;/code&gt;&lt;/a&gt; translated into the Windows CI time &lt;strong&gt;decreasing
by up to 30%&lt;/strong&gt;. 🎉&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;% decrease&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Python 3.8 (1)&lt;/td&gt;
&lt;td&gt;17m 37s&lt;/td&gt;
&lt;td&gt;12m 41s&lt;/td&gt;
&lt;td&gt;28%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python 3.8 (2)&lt;/td&gt;
&lt;td&gt;14m 58s&lt;/td&gt;
&lt;td&gt;10m 44s&lt;/td&gt;
&lt;td&gt;28%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python 3.13 (1)&lt;/td&gt;
&lt;td&gt;11m 13s&lt;/td&gt;
&lt;td&gt;9m 53s&lt;/td&gt;
&lt;td&gt;11%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python 3.13 (2)&lt;/td&gt;
&lt;td&gt;10m 39s&lt;/td&gt;
&lt;td&gt;9m 32s&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Or in graph form (&lt;strong&gt;note&lt;/strong&gt;: the data shown here is slightly more recent than the
statistics).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://sichard.ca/blog/2025/03/faster-pip-ci-on-windows-d-drive/windows-ci-times.png" alt="image" loading="lazy" /&gt;&lt;/p&gt;
&lt;h2&gt;What about ReFS or Dev Drives?&lt;span class="hx:absolute hx:-mt-20" id="what-about-refs-or-dev-drives"&gt;&lt;/span&gt;
&lt;a href="#what-about-refs-or-dev-drives" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;When I was investigating ways to improve file I/O performance on Windows, I&amp;rsquo;d discovered
&lt;strong&gt;&lt;a href="https://blogs.windows.com/windowsdeveloper/2023/06/01/dev-drive-performance-security-and-control-for-developers/"target="_blank" rel="noopener"&gt;Dev Drives&lt;/a&gt;&lt;/strong&gt;. Now, I am no Windows expert. I stopped using the OS on a daily basis
many years ago, but the TL;DR is they are a new kind of storage volume, built on the
modern ReFS (Resilient File System) filesystem and &lt;strong&gt;designed with developer workloads in
mind&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The
&lt;a href="https://github.com/astral-sh/uv/pull/3522"target="_blank" rel="noopener"&gt;uv project has had good success with using ReFS to speed up their Windows GHA jobs&lt;/a&gt;,
thus I actually started with a ReFS drive&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt; while optimising pip&amp;rsquo;s Windows CI.
Unfortunately, using a ReFS drive
&lt;a href="https://github.com/pypa/pip/pull/13129"target="_blank" rel="noopener"&gt;was noticeably slower than simply moving &lt;code&gt;TEMP&lt;/code&gt; to the D: drive&lt;/a&gt;. ReFS did save ~a
minute for &lt;code&gt;Python 3.8 (1)&lt;/code&gt; but that pales in comparison with the nearly five minutes
improvement from &lt;code&gt;TEMP=D:/Temp&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The performance improvements of ReFS/Dev Drives are primarily realized by &lt;strong&gt;deferring
malware scans&lt;/strong&gt; (Dev Drive only) and the &lt;strong&gt;inherent optimizations of the newer ReFS
filesystem&lt;/strong&gt;. Presumably the malware scans are already disabled on GitHub Actions, which
would explain why pip&amp;rsquo;s CI did not see as big of an improvement as I was expecting.&lt;/p&gt;
&lt;p&gt;Given uv&amp;rsquo;s success with ReFS, I can&amp;rsquo;t universally recommend using the D: drive over
ReFS/Dev Drives, and vice versa. &lt;strong&gt;Which one will be faster is likely workload
dependent.&lt;/strong&gt; If you would like to experiment with ReFS/Dev Drives, my Powershell script to
&lt;a href="https://github.com/pypa/pip/blob/483859b240109f9c149d7dbf32f4c7f858821e55/tools/ci/devdrive.ps1"target="_blank" rel="noopener"&gt;create such a drive can be found here&lt;/a&gt;. To invoke it, simply pass the desired
drive letter and volume size: &lt;code&gt;devdrive.ps1 -Drive R -Size 4GB&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Happy experimenting!&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Before you kill me for complaining, let me provide some context&amp;hellip; When I used to
maintain Black, I never really noticed how CI took. Most of the jobs would finish in
3-6 minutes. Short enough that I could look over my changes one last time, add notes
as comments, and then come back to merge. &lt;br&gt;
&lt;br&gt;
I did notice that Black&amp;rsquo;s test suite was
slow, taking a good few minutes on the slow laptop I had at that time. I took a look
at the test logic, &lt;a href="https://github.com/psf/black/pull/2205"target="_blank" rel="noopener"&gt;trimmed redundant work&lt;/a&gt;, and
&lt;a href="https://github.com/psf/black/pull/2196"target="_blank" rel="noopener"&gt;added pytest-xdist for parallelized testing&lt;/a&gt;. Afterwards, local test suite runs took
less a minute!&lt;br&gt;
&lt;br&gt;
I was spoiled by Black&amp;rsquo;s fast tests and CI, admittedly.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;I wrote the majority of this post in January&amp;hellip;&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;We&amp;rsquo;ve actually set it to &lt;code&gt;C:/Temp&lt;/code&gt; to work around &lt;a href="https://github.com/pypa/pip/actions/runs/12450425946/job/34757228568/#step:8:2982"target="_blank" rel="noopener"&gt;filename too long errors&lt;/a&gt;. Also,
the exact default system temporary directory may vary from worker to worker. I don&amp;rsquo;t
feel like double checking this.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;When I did my experiments, the Windows GHA runners did not support Dev Drives. Only
ReFS was supported.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>What's new in pip 25.0</title><link>https://sichard.ca/blog/2025/01/whats-new-in-pip-25.0/</link><pubDate>Fri, 31 Jan 2025 00:00:00 -0500</pubDate><guid>https://sichard.ca/blog/2025/01/whats-new-in-pip-25.0/</guid><description>
&lt;p&gt;On January 26, 2025, &lt;a href="https://discuss.python.org/t/announcement-pip-25-0-release/78392"target="_blank" rel="noopener"&gt;the pip team released pip 25.0&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Before delving into the key changes, I&amp;rsquo;d like to mention that the pip project has two new
maintainers: &lt;a href="https://github.com/notatallshaw"target="_blank" rel="noopener"&gt;Damian Shaw&lt;/a&gt; and Richard Si (that&amp;rsquo;s me!). I want to thank the pip core team
for entrusting me with the commit bit on such a foundational project like pip.&lt;/p&gt;
&lt;p&gt;Also, this is the first release cut from a GitHub Actions CI/CD workflow that implements
&lt;a href="https://docs.pypi.org/trusted-publishers/"target="_blank" rel="noopener"&gt;Trusted Publishing&lt;/a&gt; and bundles &lt;a href="https://peps.python.org/pep-0740/"target="_blank" rel="noopener"&gt;PEP 740 digital attestations&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As always, please read the &lt;a href="https://pip.pypa.io/en/latest/news/#v25-0"target="_blank" rel="noopener"&gt;changelog&lt;/a&gt; for the full list of changes.&lt;/p&gt;
&lt;h2&gt;Key features&lt;span class="hx:absolute hx:-mt-20" id="key-features"&gt;&lt;/span&gt;
&lt;a href="#key-features" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3&gt;PEP 639: SPDX License Expressions&lt;span class="hx:absolute hx:-mt-20" id="pep-639-spdx-license-expressions"&gt;&lt;/span&gt;
&lt;a href="#pep-639-spdx-license-expressions" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;About 5 months ago,
&lt;a href="https://peps.python.org/pep-0639/"target="_blank" rel="noopener"&gt;PEP 639 – Improving License Clarity with Better Package Metadata&lt;/a&gt; was approved.&lt;/p&gt;
&lt;p&gt;It added the &lt;code&gt;License-Expression&lt;/code&gt; core metadata field, which is mandated to contain a
valid &lt;a href="https://spdx.github.io/spdx-spec/v2.2.2/SPDX-license-expressions/"target="_blank" rel="noopener"&gt;SPDX expression&lt;/a&gt;. In addition, there is now a multi-use &lt;code&gt;License-File&lt;/code&gt; field so
packages can declare the path to their licensing-related files.&lt;/p&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:flex-col hx:rounded-lg hx:border hx:py-4 hx:px-4 hx:border-gray-200 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-amber-200 hx:bg-amber-100 hx:text-amber-900 hx:dark:border-amber-200/30 hx:dark:bg-amber-900/30 hx:dark:text-amber-200"&gt;
&lt;p class="hx:flex hx:items-center hx:font-medium"&gt;&lt;svg height=16px class="hx:inline-block hx:align-middle hx:mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/&gt;&lt;/svg&gt;Warning&lt;/p&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;&lt;p&gt;The old free-form &lt;code&gt;License&lt;/code&gt; field and license classifiers are &lt;strong&gt;deprecated&lt;/strong&gt;, although
there are no current plans to remove them from the metadata specification.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;It took a few months, but it is now generally supported by build backends, publishing
tools, and PyPI. With this release, &lt;code&gt;pip show&lt;/code&gt; will display the &lt;code&gt;License-Expression&lt;/code&gt; over
the &lt;code&gt;License&lt;/code&gt; field when available.&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip install &lt;span class="nv"&gt;prettytable&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;3.13.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip show prettytable
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Name: prettytable
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Version: 3.13.0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Summary: A simple Python library for easily displaying tabular data in a visually appealing ASCII table format
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Home-page: https://github.com/prettytable/prettytable
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Author:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Author-email: Luke Maurits &amp;lt;luke@maurits.id.au&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;License-Expression: BSD-3-Clause
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Location: /home/ichard26/.local/lib/python3.12/site-packages
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Requires: wcwidth
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Required-by:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Furthermore, the &lt;code&gt;License-Expression&lt;/code&gt; and &lt;code&gt;License-File&lt;/code&gt; fields are included in the JSON
report emitted by &lt;code&gt;pip install --report&lt;/code&gt; and &lt;code&gt;pip inspect&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Thank you Philippe Ombredanne, C.A.M. Gerlach, and Karolina Surma for authoring the PEP!
Doubly so to Karolina who drove the PEP to completion and contributed part of pip&amp;rsquo;s
implementation.&lt;/p&gt;
&lt;h3&gt;Network cache inherits permissions from root cache directory&lt;span class="hx:absolute hx:-mt-20" id="network-cache-inherits-permissions-from-root-cache-directory"&gt;&lt;/span&gt;
&lt;a href="#network-cache-inherits-permissions-from-root-cache-directory" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;It&amp;rsquo;s easiest to explain this change by &lt;a href="https://github.com/pypa/pip/issues/11012"target="_blank" rel="noopener"&gt;quoting the original issue&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;On a shared Linux system, we want to share pip&amp;rsquo;s cache between multiple users, so
packages are not downloaded 50 times when 50 users install the same package.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Problem is that pip would set the permissions for network cache entries so that the user
running pip is the sole user able to access them. This wasn&amp;rsquo;t intentional, but a byproduct
of &lt;code&gt;tempfile.NamedTemporaryFile&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, pip will update the entries&amp;rsquo; permissions to inherit the read/write permissions set on
the root cache directory. Thus, if the root cache directory is accessible by other users,
those users will be able to use any entries added later.&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; Thank you to Justin
van Heek for contributing this improvement!&lt;/p&gt;
&lt;h2&gt;Key bugfixes&lt;span class="hx:absolute hx:-mt-20" id="key-bugfixes"&gt;&lt;/span&gt;
&lt;a href="#key-bugfixes" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;First off, here&amp;rsquo;s a rapid-fire list of smaller, but still noteworthy bugfixes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fix a security bug allowing a specially crafted wheel to execute code during
installation.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/pypa/pip/issues/13079"target="_blank" rel="noopener"&gt;You should read this issue&lt;/a&gt; for all of the details, but essentially, a deferred
import of pip&amp;rsquo;s self check could be abused to run arbitrary code at the end of the
&lt;em&gt;same&lt;/em&gt; pip install. Reported and fixed by Caleb Brown.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The inclusion of &lt;a href="https://packaging.pypa.io/en/stable/"target="_blank" rel="noopener"&gt;packaging&lt;/a&gt; 24.2 changes how pre-release specifiers with &lt;code&gt;&amp;lt;&lt;/code&gt; and &lt;code&gt;&amp;gt;&lt;/code&gt;
behave.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Including a pre-release version with these specifiers now implies accepting pre-releases
(e.g., &lt;code&gt;&amp;lt;2.0dev&lt;/code&gt; can include &lt;code&gt;1.0rc1&lt;/code&gt;). To avoid implying pre-releases, avoid specifying
them (e.g., use &lt;code&gt;&amp;lt;2.0&lt;/code&gt;). The exception is &lt;code&gt;!=&lt;/code&gt;, which never implies pre-releases.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Certificate and proxy options are used for build dependencies&lt;span class="hx:absolute hx:-mt-20" id="certificate-and-proxy-options-are-used-for-build-dependencies"&gt;&lt;/span&gt;
&lt;a href="#certificate-and-proxy-options-are-used-for-build-dependencies" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;When pip needs to build a project for installation or metadata&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;, pip
by default sets up an isolated environment to install the build backend and its
dependencies in. These build dependencies are installed by calling pip recursively in a
subprocess.&lt;/p&gt;
&lt;p&gt;Unfortunately, the &lt;code&gt;--cert&lt;/code&gt;, &lt;code&gt;--client-cert&lt;/code&gt;, and &lt;code&gt;--proxy&lt;/code&gt; options were not passed down
to the pip subprocesses. However, &lt;code&gt;--index&lt;/code&gt; always&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt; has been, resulting in the
common problem where pip tries to install build dependencies from a private index that
uses a custom certificate&amp;hellip; that &lt;em&gt;it can&amp;rsquo;t verify&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Before pip 25.0, the workaround was to use environment variables (e.g., &lt;code&gt;PIP_CERT&lt;/code&gt;) as
those are passed down to the subprocess by the OS. &lt;strong&gt;This is no longer necessary.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;--proxy&lt;/code&gt; patch was authored in part by Luis Carlos. Thanks!&lt;/p&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:flex-col hx:rounded-lg hx:border hx:py-4 hx:px-4 hx:border-gray-200 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-amber-200 hx:bg-amber-100 hx:text-amber-900 hx:dark:border-amber-200/30 hx:dark:bg-amber-900/30 hx:dark:text-amber-200"&gt;
&lt;p class="hx:flex hx:items-center hx:font-medium"&gt;&lt;svg height=16px class="hx:inline-block hx:align-middle hx:mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/&gt;&lt;/svg&gt;Truststore regression&lt;/p&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;&lt;p&gt;While fixing this, &lt;a href="https://github.com/pypa/pip/issues/13186"target="_blank" rel="noopener"&gt;truststore was inadvertently disabled&lt;/a&gt; while installing build
dependencies. Truststore enables system CAs to work without any configuration. This will be fixed in an upcoming
pip 25.0.1 bugfix release.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:flex-col hx:rounded-lg hx:border hx:py-4 hx:px-4 hx:border-gray-200 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-green-200 hx:bg-green-100 hx:text-green-900 hx:dark:border-green-200/30 hx:dark:bg-green-900/30 hx:dark:text-green-200"&gt;
&lt;p class="hx:flex hx:items-center hx:font-medium"&gt;&lt;svg height=16px class="hx:inline-block hx:align-middle hx:mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/&gt;&lt;/svg&gt;&amp;ldquo;that seems silly!&amp;rdquo;&lt;/p&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;&lt;p&gt;Admittedly, the whole &amp;ldquo;pip calling itself&amp;rdquo; logic is not
ideal. There is a performance penalty to starting a new pip process, numerous bugs,
unexpected behaviour caused by forgetting to pass enough state (i.e. pip flags), and the
error reporting is abysmal.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/pypa/pip/issues/9081"target="_blank" rel="noopener"&gt;We would like to pay down the debt that has accumulated here&lt;/a&gt;, but installing
build dependencies in-process is likely to require substantial refactoring as the
codebase was not designed w/ repeated resolve-download-install cycles in mind.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;Faster package candidate collection&lt;span class="hx:absolute hx:-mt-20" id="faster-package-candidate-collection"&gt;&lt;/span&gt;
&lt;a href="#faster-package-candidate-collection" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;When pip initially fetches the list of distributions available for each package, it needs
to parse the &lt;a href="https://packaging.python.org/en/latest/specifications/simple-repository-api/"target="_blank" rel="noopener"&gt;HTML or JSON returned by the index&lt;/a&gt; and filter out the
distributions that obviously aren&amp;rsquo;t supported by the current platform.&lt;/p&gt;
&lt;p&gt;This release eliminated redundant URL parsing and similar processing, especially for
indices which serve absolute URLs to their distribution files (e.g., PyPI). Furthermore,
&lt;code&gt;requires-python&lt;/code&gt; checks are cached.&lt;/p&gt;
&lt;p&gt;The URL processing and &lt;code&gt;requires-python&lt;/code&gt; checks are fast individually, but they add up
once you start to use packages with hundreds of releases and thousands of distributions. A
good example is setuptools. It has ~1500 different distributions that could be eligible
for installation, leading to a fair bit of overhead:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;pip install setuptools --dry-run&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;pip 24.3.1&lt;/th&gt;
&lt;th&gt;pip 25.0&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Index page parsing&lt;/td&gt;
&lt;td&gt;90ms&lt;/td&gt;
&lt;td&gt;45ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Candidate evaluation&lt;/td&gt;
&lt;td&gt;130ms&lt;/td&gt;
&lt;td&gt;30ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Obviously the network overhead usually dominates in practice, but this is still a good
improvement! And since setuptools is a common backend, this makes the build environment
setup phase of installing from source just a bit faster for many projects. 🏎️&lt;/p&gt;
&lt;h2&gt;Upcoming removals&lt;span class="hx:absolute hx:-mt-20" id="upcoming-removals"&gt;&lt;/span&gt;
&lt;a href="#upcoming-removals" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Finally, here is a list of deprecations currently scheduled for removal in pip 25.1 (Q2
2025). As always, this schedule is subject to change. &lt;strong&gt;Any given removal may be pushed to
a future release as needed.&lt;/strong&gt;&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;Legacy (setup.py) editable installs (Deprecated since pip 24.2)&lt;/dt&gt;
&lt;dd&gt;I&amp;rsquo;ve already said enough on this deprecation, so I&amp;rsquo;m going to simply ask that you read
&lt;a href="https://github.com/pypa/pip/issues/11457"target="_blank" rel="noopener"&gt;the deprecation issue for information and advice&lt;/a&gt;. This was originally
scheduled for removal in 25.0, but it got pushed back as the removal wasn&amp;rsquo;t finished in
time.&lt;/dd&gt;
&lt;dt&gt;Legacy installed .egg distributions (Deprecated since pip 23.2)&lt;/dt&gt;
&lt;dd&gt;(&lt;a href="https://github.com/pypa/pip/issues/12330"target="_blank" rel="noopener"&gt;#12330&lt;/a&gt;) On Python 3.11 or later, support for detecting and uninstalling installed
&lt;code&gt;.egg&lt;/code&gt; distributions is deprecated. Eggs are the legacy format used to record installed
distributions. It has been long since replaced by &lt;a href="https://packaging.python.org/en/latest/specifications/recording-installed-packages/"target="_blank" rel="noopener"&gt;&lt;code&gt;.dist-info&lt;/code&gt;&lt;/a&gt;. Today, they&amp;rsquo;re most
often the product of using &lt;code&gt;setup.py install&lt;/code&gt;. The recommended solution is to reinstall
the distribution using a recent pip. FWIW, this removal has been pushed back several
times.&lt;/dd&gt;
&lt;dt&gt;Non-bare project name in egg fragment (Deprecated since pip 23.0)&lt;/dt&gt;
&lt;dd&gt;(&lt;a href="https://github.com/pypa/pip/issues/13157"target="_blank" rel="noopener"&gt;#13157&lt;/a&gt;) If you&amp;rsquo;re ever seen &lt;code&gt;#egg=mypkg&lt;/code&gt; at the end of an URL requirement, that&amp;rsquo;s
called an egg fragment. It&amp;rsquo;s the old syntax used to tell pip what project the URL is
for. They&amp;rsquo;ve been largely replaced by the standardized &lt;a href="https://packaging.python.org/en/latest/specifications/version-specifiers/#direct-references"target="_blank" rel="noopener"&gt;Direct URLs references&lt;/a&gt;. When
egg fragments were initially added, they were meant to only contain the project name.
You can technically put a version specifier in the fragment, but pip won&amp;rsquo;t respect it.
To avoid confusion, anything but a bare project name is deprecated. However, the removal
is blocked on adding support for requesting extras via the Direct URL syntax for VCS
references.&lt;/dd&gt;
&lt;dt&gt;Non-standard wheel filenames (Deprecated since pip 24.3)&lt;/dt&gt;
&lt;dd&gt;(&lt;a href="https://github.com/pypa/pip/issues/12938"target="_blank" rel="noopener"&gt;#12938&lt;/a&gt;) For historical reasons, pip does its own parsing of wheel filenames. We&amp;rsquo;d
like to replace this with the reference parser in &lt;a href="https://packaging.pypa.io/en/stable/"target="_blank" rel="noopener"&gt;packaging&lt;/a&gt;. The side-effect is that
wheels that violate the &lt;a href="https://packaging.python.org/en/latest/specifications/binary-distribution-format/#file-name-convention"target="_blank" rel="noopener"&gt;wheel filename specification&lt;/a&gt; are now rejected. Unless you use
old packaging tooling or play shenanigans with your project name or wheel filenames, you
shouldn&amp;rsquo;t be affected.&lt;/dd&gt;
&lt;dt&gt;&amp;ndash;no-python-version-warning (Deprecated since pip 25.0)&lt;/dt&gt;
&lt;dd&gt;(&lt;a href="https://github.com/pypa/pip/issues/13154"target="_blank" rel="noopener"&gt;#13154&lt;/a&gt;) The flag has long done nothing since Python 2 support was removed in pip
21.0. pip has also never warned about deprecations of any other Python version as the
flag&amp;rsquo;s help suggests.&lt;/dd&gt;
&lt;/dl&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;As long as the package&amp;rsquo;s metadata version is 2.4 or higher. There was a bit of a
kerfuffle as PEP 639 was implemented by certain backends before being accepted, thus
they generated distributions without metadata 2.4 declared (because it didn&amp;rsquo;t exist
before approval). This is technically invalid, and pip wishes to enforce specification
compliance.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Of course, this doesn&amp;rsquo;t help with old cache entries with the overly restrictive
permissions. Plus, everyone must be using pip 25.0 or else there will be a mix of
entries with the right and overly restrictive permissions, rendering the shared cache
useless.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;For example, during dependency resolution, pip may need to build metadata so it knows
what requirements &lt;code&gt;mypkg&lt;/code&gt; needs. Building a wheel and inspecting its metadata is a
valid approach, but PEP 517 allows pip to ask a backend for only metadata.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;Maybe not always, but definitely for a long time.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>What's new in pip 24.3</title><link>https://sichard.ca/blog/2024/11/whats-new-in-pip-24.3/</link><pubDate>Thu, 14 Nov 2024 00:00:00 -0500</pubDate><guid>https://sichard.ca/blog/2024/11/whats-new-in-pip-24.3/</guid><description>
&lt;p&gt;On October 27, 2024, &lt;a href="https://discuss.python.org/t/announcement-pip-24-3-release/69350"target="_blank" rel="noopener"&gt;the pip team released pip 24.3&lt;/a&gt;. This release was a significantly
smaller release with only a handful of minor fixes and improvements.&lt;/p&gt;
&lt;p&gt;This write-up also includes the hotfixes included in pip 24.3.1 released on the same day.&lt;/p&gt;
&lt;p&gt;Thank you to Damian Shaw, Hugo van Kemenade, and Stéphane Bidoul for reviewing the draft
of this write-up.&lt;/p&gt;
&lt;h2&gt;PSA: Legacy editable installs are deprecated&lt;span class="hx:absolute hx:-mt-20" id="psa-legacy-editable-installs-are-deprecated"&gt;&lt;/span&gt;
&lt;a href="#psa-legacy-editable-installs-are-deprecated" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;TL;DR, &lt;strong&gt;don&amp;rsquo;t panic&lt;/strong&gt;. The &lt;code&gt;-e&lt;/code&gt; option is &lt;em&gt;not&lt;/em&gt; deprecated, but the way it works under
the hood will change, potentially necessitating changes to how your project is packaged.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you aren&amp;rsquo;t aware, since pip 24.2, &lt;strong&gt;legacy&lt;/strong&gt; editable installs are deprecated and
&lt;strong&gt;support is scheduled for removal in pip 25.0 in the new year&lt;/strong&gt; (at the time of writing).&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip install -e temp/pip-test-package
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Obtaining file:///home/ichard26/dev/oss/pip/temp/pip-test-package
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Preparing metadata (setup.py) ... done
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Installing collected packages: version_pkg
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; DEPRECATION: Legacy editable install of version_pkg==0.1 from file:///home/ichard26/dev/oss/pip/temp/pip-test-package (setup.py develop) is deprecated. pip 25.0 will enforce this behaviour change. A possible replacement is to add a pyproject.toml or enable --use-pep517, and use setuptools &amp;gt;= 64. If the resulting installation is not behaving as expected, try using --config-settings editable_mode=compat. Please consult the setuptools documentation for more information. Discussion can be found at https://github.com/pypa/pip/issues/11457
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Running setup.py develop for version_pkg
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Successfully installed version_pkg-0.1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Editable installs are &lt;em&gt;not&lt;/em&gt; deprecated, but pip will stop running the setuptools specific
&lt;code&gt;setup.py&lt;/code&gt; file under the hood. Instead, pip is transitioning to exclusively using the
standardized mechanism to request an editable install from the project&amp;rsquo;s backend defined
in &lt;a href="https://peps.python.org/pep-0660/"target="_blank" rel="noopener"&gt;PEP 660&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re confused, don&amp;rsquo;t worry. The implementation details are admittedly a bit complex,
but all you need to know is that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You will see this deprecation notice &lt;strong&gt;if the package&lt;/strong&gt; you are trying to install has
&lt;strong&gt;setuptools as its build backend AND&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;EITHER this package does not have a valid &lt;a href="https://packaging.python.org/en/latest/specifications/pyproject-toml/#declaring-build-system-dependencies-the-build-system-table"target="_blank" rel="noopener"&gt;build system declaration&lt;/a&gt;&lt;/strong&gt;, thus pip
will opt-out of the modern mechanisms and run &lt;code&gt;setup.py&lt;/code&gt; directly as required&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OR this package’s build system declaration explicitly requests a version setuptools
older than version 64&lt;/strong&gt; which lacks support for the modern editable installation
mechanism (PEP 660)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The easiest way to address the deprecation is to &lt;strong&gt;add a &lt;code&gt;pyproject.toml&lt;/code&gt; file that
declares your package&amp;rsquo;s build system to be setuptools&lt;/strong&gt; (see the guide
&lt;a href="https://packaging.python.org/en/latest/guides/modernize-setup-py-project/"target="_blank" rel="noopener"&gt;&amp;ldquo;How to modernize a setup.py based project?&amp;rdquo;&lt;/a&gt; for more details)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You can keep your &lt;code&gt;setup.py&lt;/code&gt; file.&lt;/strong&gt; It&amp;rsquo;s a configuration file for setuptools, and
setuptools still supports it. pip simply won&amp;rsquo;t be running it directly anymore,
delegating to setuptools (see the discussion &lt;a href="https://packaging.python.org/en/latest/discussions/setup-py-deprecated/"target="_blank" rel="noopener"&gt;&amp;ldquo;Is setup.py deprecated?&amp;rdquo;&lt;/a&gt; for more
details).
&lt;ul&gt;
&lt;li&gt;If you wish, you can migrate your packaging setup to &lt;code&gt;pyproject.toml&lt;/code&gt; entirely, but
this isn&amp;rsquo;t necessary&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;For more advice, please &lt;a href="https://github.com/pypa/pip/issues/11457"target="_blank" rel="noopener"&gt;see the issue tracking this deprecation&lt;/a&gt; and
&lt;a href="https://github.com/pypa/pip/issues/11457#issuecomment-2439645125"target="_blank" rel="noopener"&gt;this comment&lt;/a&gt;.&lt;/strong&gt; You can also read my &lt;a href="https://sichard.ca/blog/2024/08/whats-new-in-pip-24.2/"&gt;write-up on pip 24.2&lt;/a&gt; which discusses the context
behind the deprecation.&lt;/p&gt;
&lt;h2&gt;QoL improvements&lt;span class="hx:absolute hx:-mt-20" id="qol-improvements"&gt;&lt;/span&gt;
&lt;a href="#qol-improvements" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3&gt;Recursive requirement files are now detected&lt;span class="hx:absolute hx:-mt-20" id="recursive-requirement-files-are-now-detected"&gt;&lt;/span&gt;
&lt;a href="#recursive-requirement-files-are-now-detected" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Imagine you had a requirement file that recursively included itself:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;-r requirements.txt&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;If you ran &lt;code&gt;pip install -r requirements.txt&lt;/code&gt;, you&amp;rsquo;d get a nasty crash as pip kept
following the loop until it reached the recursion limit:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ERROR: Exception:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Traceback (most recent call last):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[trimmed...]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; File &amp;#34;/home/ichard26/dev/oss/pip/src/pip/_internal/cli/req_command.py&amp;#34;, line 255, in get_requirements
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; for parsed_req in parse_requirements(
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; File &amp;#34;/home/ichard26/dev/oss/pip/src/pip/_internal/req/req_file.py&amp;#34;, line 151, in parse_requirements
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; for parsed_line in parser.parse(filename, constraint):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; File &amp;#34;/home/ichard26/dev/oss/pip/src/pip/_internal/req/req_file.py&amp;#34;, line 332, in parse
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; yield from self._parse_and_recurse(filename, constraint)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; File &amp;#34;/home/ichard26/dev/oss/pip/src/pip/_internal/req/req_file.py&amp;#34;, line 361, in _parse_and_recurse
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; yield from self._parse_and_recurse(req_path, nested_constraint)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; File &amp;#34;/home/ichard26/dev/oss/pip/src/pip/_internal/req/req_file.py&amp;#34;, line 361, in _parse_and_recurse
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; yield from self._parse_and_recurse(req_path, nested_constraint)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; File &amp;#34;/home/ichard26/dev/oss/pip/src/pip/_internal/req/req_file.py&amp;#34;, line 361, in _parse_and_recurse
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; yield from self._parse_and_recurse(req_path, nested_constraint)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; [Previous line repeated 974 more times]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; File &amp;#34;/home/ichard26/dev/oss/pip/src/pip/_internal/req/req_file.py&amp;#34;, line 337, in _parse_and_recurse
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; for line in self._parse_file(filename, constraint):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[trimmed...]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;RecursionError: maximum recursion depth exceeded&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This example is admittedly a bit contrived, but it can happen if you&amp;rsquo;re referencing
requirement files in a chain and forget what you&amp;rsquo;ve already included. Now, pip will detect
this loop and raise a proper error.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip install -r test.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ERROR: /home/ichard26/dev/oss/pip/test.txt recursively references itself in test.txt
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This was contributed by Kuntal Majumder (@hellozee) in &lt;a href="https://github.com/pypa/pip/pull/12877"target="_blank" rel="noopener"&gt;PR #12877&lt;/a&gt;. ✨&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(There was a &lt;a href="https://github.com/pypa/pip/issues/13046"target="_blank" rel="noopener"&gt;bug where the error would be raised too often&lt;/a&gt;, even when no actual
recursion was present. This was promptly addressed in pip 24.3.1.)&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Installed packages with broken dependency metadata now raise a proper error&lt;span class="hx:absolute hx:-mt-20" id="installed-packages-with-broken-dependency-metadata-now-raise-a-proper-error"&gt;&lt;/span&gt;
&lt;a href="#installed-packages-with-broken-dependency-metadata-now-raise-a-proper-error" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Since pip 24.1,
&lt;a href="https://pradyunsg.me/blog/2024/05/13/pip-24-1-betas/"target="_blank" rel="noopener"&gt;non standard-compliant versions or dependency specifiers are unsupported&lt;/a&gt;.
While pip will ignore packages with this legacy invalid metadata when possible, in other
cases, the offending package had to be uninstalled to allow pip to function.&lt;/p&gt;
&lt;p&gt;Unfortunately, similar to the issue above, pip would just crash and leave the user a
horrible traceback in these cases.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip install celery
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Requirement already satisfied: celery in ./venv/lib/python3.12/site-packages (4.4.7)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ERROR: Exception:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[trimmed...]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; File &amp;#34;/home/ichard26/dev/oss/pip/src/pip/_internal/resolution/resolvelib/candidates.py&amp;#34;, line 401, in iter_dependencies
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; for r in self.dist.iter_dependencies():
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; File &amp;#34;/home/ichard26/dev/oss/pip/src/pip/_internal/metadata/importlib/_dists.py&amp;#34;, line 215, in iter_dependencies
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; req = get_requirement(req_string.strip())
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; File &amp;#34;/home/ichard26/dev/oss/pip/src/pip/_internal/utils/packaging.py&amp;#34;, line 45, in get_requirement
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; return Requirement(req_string)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ^^^^^^^^^^^^^^^^^^^^^^^
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; File &amp;#34;/home/ichard26/dev/oss/pip/src/pip/_vendor/packaging/requirements.py&amp;#34;, line 38, in __init__
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; raise InvalidRequirement(str(e)) from e
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;pip._vendor.packaging.requirements.InvalidRequirement: Expected matching RIGHT_PARENTHESIS for LEFT_PARENTHESIS, after version specifier
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; pytz (&amp;gt;dev)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ~^
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;To address this, a new diagnostic error has been added when encountering a broken
pre-installed package.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip install celery
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Requirement already satisfied: celery in ./venv/lib/python3.12/site-packages (4.4.7)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;error: invalid-installed-package
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;× Cannot process installed package celery 4.4.7 in &amp;#39;/home/ichard26/dev/oss/pip/venv/lib/python3.12/site-packages&amp;#39; because it has an invalid requirement:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;│ Expected matching RIGHT_PARENTHESIS for LEFT_PARENTHESIS, after version specifier
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;│ pytz (&amp;gt;dev)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;│ ^
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;╰─&amp;gt; Starting with pip 24.1, packages with invalid requirements can not be processed.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;hint: To proceed this package must be uninstalled.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Admittedly, the error is a bit hard to read, but with time, pip&amp;rsquo;s errors should improve.
In the interim, you can quickly tell what package is causing the problem (celery) and how
to fix it (uninstall it).&lt;/p&gt;
&lt;h2&gt;macOS 10.12 is resupported&lt;span class="hx:absolute hx:-mt-20" id="macos-1012-is-resupported"&gt;&lt;/span&gt;
&lt;a href="#macos-1012-is-resupported" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;If you&amp;rsquo;re part of the tiny group that still uses modern pip on macOS 10.12, you would&amp;rsquo;ve
learned first-hand that truststore did not support macOS 10.12, &lt;a href="https://github.com/pypa/pip/issues/12901"target="_blank" rel="noopener"&gt;breaking pip&lt;/a&gt;.
Good news though, pip 24.3 restores support by vendoring in an updated version of
&lt;code&gt;truststore&lt;/code&gt; that uses older macOS APIs when needed.&lt;/p&gt;
&lt;p&gt;This brings the pip project one step closer to being able to use system HTTPS certificates
when possible, all without blowing up in odd situations. There are still
&lt;a href="https://github.com/pypa/pip/pull/12918"target="_blank" rel="noopener"&gt;some known issues with private certificates&lt;/a&gt;, however.&lt;/p&gt;
&lt;h2&gt;Invalid wheel filenames are deprecated&lt;span class="hx:absolute hx:-mt-20" id="invalid-wheel-filenames-are-deprecated"&gt;&lt;/span&gt;
&lt;a href="#invalid-wheel-filenames-are-deprecated" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Unless you use ancient packages before the advent of modern packaging tooling
AND are incredibly unlucky, this deprecation will NOT affect you. This is mostly for
those interested in the packaging nitty gritty.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you have any sort of familiarity with Python packaging, you&amp;rsquo;d have heard of wheels.
Wheels are a binary distribution format for Python packages. They are pre-built, which
makes installing them particularly easy: just unzip them and copy the contents to the
&lt;code&gt;site-packages&lt;/code&gt; directory.&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Something you might not know is that
&lt;a href="https://packaging.python.org/en/latest/specifications/binary-distribution-format/#file-name-convention"target="_blank" rel="noopener"&gt;their filenames follow a certain structure&lt;/a&gt;. This enables installers to
extract essential metadata without inspecting the wheel&amp;rsquo;s contents. As an example, let&amp;rsquo;s
take a look at the newest wheel for pip.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;pip-24.3.1-py3-none-any.whl&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Here, we know three pieces of information immediately:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pip&lt;/code&gt; is the package name&lt;/li&gt;
&lt;li&gt;&lt;code&gt;24.3.1&lt;/code&gt; is the version&lt;/li&gt;
&lt;li&gt;&lt;code&gt;py3-none-any&lt;/code&gt; is the compatibility tag.
&lt;a href="https://sichard.ca/blog/2024/08/whats-new-in-pip-24.2/#pip-check-just-got-a-bit-stricter"&gt;I went over compatibility tags in my previous pip write-up&lt;/a&gt;, but
essentially, &lt;code&gt;py3-none-any&lt;/code&gt; is code for this package supports “any Python 3 version”,
“any Python ABI”, and “any architecture.”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unfortunately, some wheels out in the wild have improper names, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;translate_toolkit-1.9.0_pinterest3-py27-none-any.whl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;j2cli-0.3.0_1-py2-none-any.whl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;password_strength-0.0.1_1-py2-none-any.whl&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;According to the &lt;a href="https://packaging.python.org/en/latest/specifications/version-specifiers/#implicit-post-releases"target="_blank" rel="noopener"&gt;version specifier standard&lt;/a&gt;, underscores can be used to separate the
release segment from any pre/post/development release segments. However, they cannot be
used to separate an implicit post-release.&lt;/p&gt;
&lt;p&gt;In other words, &lt;code&gt;1.2.0_post1&lt;/code&gt; (explicit) is valid, but &lt;code&gt;1.2.0_1&lt;/code&gt; (implicit) is
not.&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;For historical reasons, pip has accepted wheels with this error. This is inconsistent and
confusing, thus starting with pip 24.3, these wheels are no longer supported.&lt;/p&gt;
&lt;p&gt;Frankly, virtually no packages today have this issue, so you can forget this deprecation
even exists&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;, but it is a perfect opportunity to learn about the
complexity found in Python packaging. Many things may look easy in packaging, but often
they are not :)&lt;/p&gt;
&lt;h2&gt;Other changes&lt;span class="hx:absolute hx:-mt-20" id="other-changes"&gt;&lt;/span&gt;
&lt;a href="#other-changes" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;iOS wheels are provisionally supported: pip will now recognize and accept iOS tagged
wheels, although whether pip properly supports them is something I don&amp;rsquo;t know.&lt;/li&gt;
&lt;li&gt;Installing pip in editable mode in a virtual environment on Windows doesn&amp;rsquo;t result in a
&lt;a href="https://github.com/pypa/pip/issues/12666"target="_blank" rel="noopener"&gt;data race crash anymore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;--target&lt;/code&gt; option is ignored while pip prepares a build environment during
installation, which often caused &lt;a href="https://github.com/pypa/pip/issues/8438"target="_blank" rel="noopener"&gt;confusing crashes&lt;/a&gt; when it was configured globally in
a pip configuration file or via the &lt;code&gt;PIP_TARGET&lt;/code&gt; environment variable.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Of course, it&amp;rsquo;s not this simple in reality, but there is no &amp;ldquo;build step&amp;rdquo; involved
which makes them trivial compared to source distributions (typically .tar.gz).&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;To declare a post-release without the &lt;code&gt;post&lt;/code&gt; prefix, you must use the &lt;code&gt;-&lt;/code&gt; separator,
e.g. &lt;code&gt;1.2.0-1&lt;/code&gt;. Naturally, this raises the question of what to do when this version is
placed in the wheel filename as dashes are reserved as wheel filename separators. The
answer is that the version should be normalized when placed in a wheel filename, and
fortunately for us, &lt;code&gt;1.2.0-1&lt;/code&gt; normalizes to &lt;code&gt;1.2.0.post1&lt;/code&gt;.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;&lt;a href="https://github.com/pypa/pip/pull/12918"target="_blank" rel="noopener"&gt;While reviewing the deprecation PR&lt;/a&gt;, I scraped the index pages for the top
8000 PyPI packages and could only find 4 ancient wheels that have this quirk in their
filename.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>What's new in pip 24.2 — or why legacy editable installs are deprecated</title><link>https://sichard.ca/blog/2024/08/whats-new-in-pip-24.2/</link><pubDate>Mon, 26 Aug 2024 00:00:00 -0400</pubDate><guid>https://sichard.ca/blog/2024/08/whats-new-in-pip-24.2/</guid><description>
&lt;p&gt;On July 28, 2024, the pip team released &lt;a href="https://discuss.python.org/t/announcement-pip-24-2-release/59402"target="_blank" rel="noopener"&gt;pip 24.2&lt;/a&gt;. As a recent addition to the pip core
team, I&amp;rsquo;ll walk you through the noteworthy or interesting changes from pip 24.2. Let&amp;rsquo;s go!&lt;/p&gt;
&lt;blockquote&gt;
&lt;h4&gt;PSA, PSA, PSA 📣&lt;span class="hx:absolute hx:-mt-20" id="psa-psa-psa-"&gt;&lt;/span&gt;
&lt;a href="#psa-psa-psa-" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;If you&amp;rsquo;re using setuptools, do not have a &lt;code&gt;pyproject.toml&lt;/code&gt;, and use pip&amp;rsquo;s &lt;code&gt;-e&lt;/code&gt; flag, you
may be impacted by the &lt;a href="https://github.com/pypa/pip/issues/11457"target="_blank" rel="noopener"&gt;deprecation of legacy editable installs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you&amp;rsquo;re an user of setuptools and came to this post after seeing the legacy editable
install deprecation warning&lt;/strong&gt;,
&lt;a href="https://github.com/pypa/pip/issues/11457"target="_blank" rel="noopener"&gt;you should first read the deprecation issue for solutions, &lt;strong&gt;not here&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can come back and read this after for more historical context on the deprecation,
however! It&amp;rsquo;s a good read, I promise.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here&amp;rsquo;s a &lt;a href="https://pip.pypa.io/en/stable/news/#v24-2"target="_blank" rel="noopener"&gt;link to the 24.2 changelog&lt;/a&gt; if you&amp;rsquo;d like the full list of changes.&lt;/p&gt;
&lt;h2&gt;Legacy editable installs are deprecated&lt;span class="hx:absolute hx:-mt-20" id="legacy-editable-installs-are-deprecated"&gt;&lt;/span&gt;
&lt;a href="#legacy-editable-installs-are-deprecated" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;TL;DR, &lt;strong&gt;don&amp;rsquo;t panic&lt;/strong&gt;. The &lt;code&gt;-e&lt;/code&gt; option is &lt;em&gt;not&lt;/em&gt; deprecated, but the way it works under
the hood will change, potentially necessitating changes to how your project is packaged.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Over the last decade, there has been a major transition towards &lt;strong&gt;standardized
mechanisms&lt;/strong&gt; for packaging Python projects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://peps.python.org/pep-0517/"target="_blank" rel="noopener"&gt;PEP 517&lt;/a&gt;&lt;/strong&gt;: introduced an interface where frontends (e.g. the pip installer) can
interact with a build backend (e.g. Poetry&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;), including asking it to build a
wheel for later installation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://peps.python.org/pep-0518/"target="_blank" rel="noopener"&gt;PEP 518&lt;/a&gt;&lt;/strong&gt;: introduced &lt;code&gt;pyproject.toml&lt;/code&gt; and defined a standard format for declaring
build-time dependencies using this file&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://peps.python.org/pep-0621/"target="_blank" rel="noopener"&gt;PEP 621&lt;/a&gt;&lt;/strong&gt;: defined a standard format for declaring project metadata in
&lt;code&gt;pyproject.toml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://peps.python.org/pep-0660/"target="_blank" rel="noopener"&gt;PEP 660&lt;/a&gt;&lt;/strong&gt;: augmented the PEP 517 interface to define a way to ask a build backend to
prepare an editable install&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These PEPs are what enable the use of alternative backends like &lt;a href="https://python-poetry.org/"target="_blank" rel="noopener"&gt;Poetry&lt;/a&gt;, &lt;a href="https://hatch.pypa.io/latest/"target="_blank" rel="noopener"&gt;Hatch&lt;/a&gt;, and
&lt;a href="https://scikit-build-core.readthedocs.io/"target="_blank" rel="noopener"&gt;scikit-build-core&lt;/a&gt; &lt;em&gt;without needing to add specific support for each backend&lt;/em&gt; in every
frontend (e.g. pip, tox, uv). Instead of running &lt;code&gt;setup.py bdist_wheel&lt;/code&gt; to ask setuptools
to build a wheel for installation, pip can simply call the &lt;code&gt;build_wheel&lt;/code&gt;
hook&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; from PEP 517 which all backends are guaranteed to have.&lt;/p&gt;
&lt;p&gt;As each standard matured, &lt;strong&gt;pip has progressively deprecated and removed support for
legacy&lt;/strong&gt; (and often setuptools-specific) &lt;strong&gt;mechanisms&lt;/strong&gt; to reduce technical debt and
ensure that pip can continue to evolve.&lt;/p&gt;
&lt;p&gt;Editable installs—the thing you get from &lt;code&gt;pip install -e &amp;lt;path&amp;gt;&lt;/code&gt;—used to be a setuptools
only feature, requiring pip to call the project&amp;rsquo;s &lt;code&gt;setup.py&lt;/code&gt; with the &lt;code&gt;develop&lt;/code&gt;
sub-command. However, this stopped being the case under &lt;a href="https://peps.python.org/pep-0660/"target="_blank" rel="noopener"&gt;PEP 660&lt;/a&gt;. As long as your
project&amp;rsquo;s backend supports PEP 660, &lt;code&gt;pip install -e&lt;/code&gt; will continue to work. No setuptools
required. (Although modern setuptools works fine as well.)&lt;/p&gt;
&lt;h3&gt;OK, so what now?&lt;span class="hx:absolute hx:-mt-20" id="ok-so-what-now"&gt;&lt;/span&gt;
&lt;a href="#ok-so-what-now" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;This release,
&lt;a href="https://github.com/pypa/pip/issues/11457"target="_blank" rel="noopener"&gt;pip has deprecated support for the &lt;code&gt;setup.py develop&lt;/code&gt; fallback&lt;/a&gt;
which is used when a project lacks support for modern editable installs. &lt;strong&gt;It will be
removed in pip 25.0 (Q1 2025)&lt;/strong&gt; after which projects MUST support PEP 660 to perform an
editable install. Affected projects will see this deprecation warning:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;pip install -e temp/pip-test-package
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Obtaining file:///home/ichard26/dev/oss/pip/temp/pip-test-package
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Preparing metadata (setup.py) ... done
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Installing collected packages: version_pkg
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; DEPRECATION: Legacy editable install of version_pkg==0.1 from file:///home/ichard26/dev/oss/pip/temp/pip-test-package (setup.py develop) is deprecated. pip 25.0 will enforce this behaviour change. A possible replacement is to add a pyproject.toml or enable --use-pep517, and use setuptools &amp;gt;= 64. If the resulting installation is not behaving as expected, try using --config-settings editable_mode=compat. Please consult the setuptools documentation for more information. Discussion can be found at https://github.com/pypa/pip/issues/11457
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Running setup.py develop for version_pkg
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Successfully installed version_pkg-0.1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;In practice, this usually means one of two things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;pyproject.toml&lt;/code&gt; file doesn&amp;rsquo;t exist at all, thus pip will opt-out of the PEP 517
interface, running &lt;code&gt;setup.py&lt;/code&gt; as required&lt;/li&gt;
&lt;li&gt;The declared setuptools version (in the &lt;code&gt;[build-system].requires&lt;/code&gt; field) is too old and
doesn&amp;rsquo;t support PEP 660, i.e. anything older than &lt;a href="https://setuptools.pypa.io/en/latest/history.html#v64-0-0"target="_blank" rel="noopener"&gt;setuptools 64.0.0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For affected projects, the best solution is likely to &lt;strong&gt;use a modern version of setuptools
and declare this via &lt;code&gt;pyproject.toml&lt;/code&gt;&lt;/strong&gt;. You can place your project metadata in this file
as well, but it&amp;rsquo;s &lt;em&gt;not&lt;/em&gt; required to stop the legacy editable deprecation warnings.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# pyproject.toml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;build-system&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;requires&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;setuptools &amp;gt;= 64&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;build-backend&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;setuptools.build_meta&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: if you have other dependencies needed to run &lt;code&gt;setup.py&lt;/code&gt; or otherwise build your
package, you should add them to the &lt;code&gt;[&amp;quot;setuptools &amp;gt;= 64&amp;quot;]&lt;/code&gt; list. This field is
equivalent to setuptools&amp;rsquo; &lt;code&gt;setup_requires&lt;/code&gt; parameter.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Alternatively, projects can pass the &lt;code&gt;--enable-pep517&lt;/code&gt; flag to force pip to use the PEP
517 interface, including the editable install extensions from PEP 660. If no backend is
declared, pip will assume that the project uses setuptools and ensure it&amp;rsquo;s available in
the isolated build environment.&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt; As long as setuptools 64+ still supports your
Python version, a modern editable install will be performed. Once the legacy mechanism is
removed, &lt;code&gt;--use-pep517&lt;/code&gt; will have no effect and will essentially be enabled by default.&lt;/p&gt;
&lt;p&gt;Finally, if you don&amp;rsquo;t like either of those solutions, you can always throw away setuptools
and switch to a different backend entirely. I&amp;rsquo;ve mentioned a few earlier, but there are
even more options to choose from if you&amp;rsquo;re willing to do some research and play around.
I&amp;rsquo;d start with with the
&lt;a href="https://packaging.python.org/en/latest/guides/tool-recommendations/#build-backends"target="_blank" rel="noopener"&gt;Python Packaging User Guide&amp;rsquo;s list of the commonly used backends&lt;/a&gt; if you want
to replace setuptools.&lt;/p&gt;
&lt;p&gt;If you stick with setuptools, one potential snag is that setuptools has introduced a new
kind of editable installs while rolling out PEP 660 by default:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If a package uses the &amp;ldquo;src&amp;rdquo; layout or otherwise places the source code in a separate
top-level directory, setuptools will create a static &lt;code&gt;.pth&lt;/code&gt; file to extend the Python
import path with that directory&lt;/li&gt;
&lt;li&gt;If a package uses the &amp;ldquo;flat&amp;rdquo; layout, setuptools will install a custom import hook which
intercepts imports and &amp;ldquo;redirects&amp;rdquo; them to the original modules (so auxiliary top-level
files like &lt;code&gt;noxfile.py&lt;/code&gt; or &lt;code&gt;setup.py&lt;/code&gt; or a &lt;code&gt;tests&lt;/code&gt; package aren&amp;rsquo;t importable)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the &lt;a href="https://setuptools.pypa.io/en/latest/userguide/development_mode.html#legacy-behavior"target="_blank" rel="noopener"&gt;legacy behaviour is desired&lt;/a&gt;, one must pass
&lt;code&gt;--config-settings editable_mode=compat&lt;/code&gt;. There is also one more method:
&lt;a href="https://setuptools.pypa.io/en/latest/userguide/development_mode.html#strict-editable-installs"target="_blank" rel="noopener"&gt;strict editable installs&lt;/a&gt;. They behave closer to a normal installation, but are
implemented in an entirely different way from &lt;code&gt;setup.py&lt;/code&gt; or the default method described
above.&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(&lt;strong&gt;Update&lt;/strong&gt;: a setuptools maintainer reached out to strict editable installs are not
enabled by default. It turns out that the situation behind potential breakage is a bit
more nuanced. Sorry!)&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt;: Static analysis tools including mypy, pyright, and pylint may not function
properly when setuptools uses an import hook to implement an editable install.
&lt;a href="https://github.com/pypa/setuptools/issues/3518"target="_blank" rel="noopener"&gt;This is a known issue&lt;/a&gt;. The recommended workaround is to pass
&lt;code&gt;--config-settings editable_mode=compat&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For more details using setuptools with &lt;code&gt;pyproject.toml&lt;/code&gt;, you should read their
documentation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;⭐ &lt;a href="https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html"target="_blank" rel="noopener"&gt;User guide to using &lt;code&gt;pyproject.toml&lt;/code&gt;&lt;/a&gt; ⭐&lt;/li&gt;
&lt;li&gt;⭐ &lt;a href="https://setuptools.pypa.io/en/latest/userguide/development_mode.html"target="_blank" rel="noopener"&gt;setuptools&amp;rsquo; editable install docs&lt;/a&gt; ⭐&lt;/li&gt;
&lt;li&gt;&lt;a href="https://setuptools.pypa.io/en/latest/build_meta.html"target="_blank" rel="noopener"&gt;setuptools&amp;rsquo; build system docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you&amp;rsquo;d like to learn more, especially about the history of why and how these standards
came to exist, &lt;strong&gt;I strongly recommend
&lt;a href="https://blog.ganssle.io/articles/2021/10/setup-py-deprecated.html"target="_blank" rel="noopener"&gt;reading Paul Ganssle&amp;rsquo;s excellent &amp;ldquo;Why you shouldn&amp;rsquo;t invoke setup.py directly&amp;rdquo; article&lt;/a&gt;&lt;/strong&gt;.
It&amp;rsquo;s &lt;em&gt;long&lt;/em&gt;, but packs way more information than I could ever &lt;em&gt;hope&lt;/em&gt; to cover.&lt;/p&gt;
&lt;h2&gt;System HTTPS certificates by default(*)&lt;span class="hx:absolute hx:-mt-20" id="system-https-certificates-by-default"&gt;&lt;/span&gt;
&lt;a href="#system-https-certificates-by-default" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;If you&amp;rsquo;ve ever used pip in a corporate environment, there is a good chance that you&amp;rsquo;ve
encountered a network or SSL verification error because the remote index server (PyPI or a
private index) returned a HTTPS certificate that couldn&amp;rsquo;t be verified.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;pip install toto
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Looking in indexes: https://repos.company/api/pypi/python_pypi/simple
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by &amp;#39;SSLError(SSLError(0, &amp;#39;unknown error (_ssl.c:3630)&amp;#39;),)&amp;#39;: /api/pypi/python_pypi/simple/toto/
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;WARNING: Retrying (Retry(total=3, connect=None, read=None, redirect=None, status=None)) after connection broken by &amp;#39;SSLError(SSLError(0, &amp;#39;unknown error (_ssl.c:3630)&amp;#39;),)&amp;#39;: /api/pypi/python_pypi/simple/toto/
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;WARNING: Retrying (Retry(total=2, connect=None, read=None, redirect=None, status=None)) after connection broken by &amp;#39;SSLError(SSLError(0, &amp;#39;unknown error (_ssl.c:3630)&amp;#39;),)&amp;#39;: /api/pypi/python_pypi/simple/toto/
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;WARNING: Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by &amp;#39;SSLError(SSLError(0, &amp;#39;unknown error (_ssl.c:3630)&amp;#39;),)&amp;#39;: /api/pypi/python_pypi/simple/toto/
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;WARNING: Retrying (Retry(total=0, connect=None, read=None, redirect=None, status=None)) after connection broken by &amp;#39;SSLError(SSLError(0, &amp;#39;unknown error (_ssl.c:3630)&amp;#39;),)&amp;#39;: /api/pypi/python_pypi/simple/toto/
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Could not fetch URL https://repos.company/api/pypi/python_pypi/simple/toto/: There was a problem confirming the ssl certificate: HTTPSConnectionPool(host= &amp;#39;repos.company&amp;#39;, port=443): Max retries exceeded with url: /api/pypi/python_pypi/simple/toto/ (Caused by SSLError(SSLError(0, &amp;#39;unknown error (_ssl.c:3630)&amp;#39;),)) - skipping
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ERROR: Could not find a version that satisfies the requirement toto (from versions: none)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ERROR: No matching distribution found for toto
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The problem is that while your system may be configured with the required certificate
authorities, &lt;em&gt;pip had no ability to use those system CAs&lt;/em&gt;. This resulted in a confusing
user experience, because while your browser was likely able to access the index server,
pip &lt;a href="https://github.com/pypa/pip/issues/12560"target="_blank" rel="noopener"&gt;blew up&lt;/a&gt; with a non-obvious &lt;a href="https://github.com/pypa/pip/issues/10777"target="_blank" rel="noopener"&gt;error&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To be able to verify HTTPS certificates, since the early days, pip has shipped with the
&lt;a href="https://pypi.org/project/certifi/"target="_blank" rel="noopener"&gt;certifi&lt;/a&gt; CA bundle, which is simply a Python repackaging of the Mozilla CA bundle. To get
pip to verify HTTPS certificates not trusted by certifi, you had to pass your own bundle
via the &lt;code&gt;--cert&lt;/code&gt; option which is clunky.&lt;/p&gt;
&lt;p&gt;In pip 22.2, pip gained the ability to use &lt;a href="https://pypi.org/project/truststore/"target="_blank" rel="noopener"&gt;truststore&lt;/a&gt; for HTTPS certificate validation,
enabling pip to use the system certificate store instead of solely relying on certifi. The
integration was experimental; users had to have truststore already installed and opt-in
via &lt;code&gt;--use-feature=truststore&lt;/code&gt;, but it was a step forward.&lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt; The plan was to enable
truststore by default once the feature matured.&lt;/p&gt;
&lt;p&gt;Two years later, in this pip release, &lt;strong&gt;truststore is now enabled by default&lt;/strong&gt;.&lt;sup id="fnref:6"&gt;&lt;a href="#fn:6" class="footnote-ref" role="doc-noteref"&gt;6&lt;/a&gt;&lt;/sup&gt;
Thus, assuming your system is configured properly with the required CAs, pip should simply
&amp;ldquo;just work&amp;rdquo; even in many corporate environments. 🎉&lt;/p&gt;
&lt;p&gt;There is one major problem though—you saw that asterisk, right? &lt;strong&gt;Truststore only works on
Python 3.10 or higher&lt;/strong&gt;. pip will continue to exclusively use its certifi bundle on Python
3.9 or 3.8. Additionally,
&lt;a href="https://github.com/pypa/pip/issues/12892"target="_blank" rel="noopener"&gt;any Python implementation that does not implement the &lt;code&gt;ssl&lt;/code&gt; module APIs needed by truststore&lt;/a&gt;
will not be able to use the system trust store either. In these situations, you will need
to continue using &lt;code&gt;--cert&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Major thanks goes to &lt;a href="https://sethmlarson.dev/"target="_blank" rel="noopener"&gt;@sethmlarson&lt;/a&gt; for co-authoring truststore and writing patches
improving and fixing pip&amp;rsquo;s truststore integration!&lt;/p&gt;
&lt;h2&gt;Performance optimizations, duh!&lt;span class="hx:absolute hx:-mt-20" id="performance-optimizations-duh"&gt;&lt;/span&gt;
&lt;a href="#performance-optimizations-duh" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;In this release, several optimizations of the environment inspection, file download,
dependency resolution, and installation logic landed. While individually they are minor
optimizations, together they provide a noticeable performance uplift in a variety of
workloads!&lt;/p&gt;
&lt;h3&gt;Faster discovery of installed packages&lt;span class="hx:absolute hx:-mt-20" id="faster-discovery-of-installed-packages"&gt;&lt;/span&gt;
&lt;a href="#faster-discovery-of-installed-packages" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;First of all, on Python 3.11 or higher, pip is significantly faster to discover installed
packages (especially if there are a lot of &amp;rsquo;em). This not only makes &lt;code&gt;pip list&lt;/code&gt; faster,
but also makes pip generally faster as it has to figure out what packages are already
installed &lt;a href="https://github.com/pypa/pip/pull/12656#issuecomment-2097040577"target="_blank" rel="noopener"&gt;quite frequently throughout its operation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;These are the before and after runtimes of &lt;code&gt;pip list&lt;/code&gt; in my pip development environment
which has 75 packages installed:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# before: pip list (24.1)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;real 0m0.227s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;user 0m0.192s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;sys 0m0.035s&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# after: pip list (24.2)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;real 0m0.207s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;user 0m0.170s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;sys 0m0.036s&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;On slower systems, the performance improvement will be greater.&lt;sup id="fnref:7"&gt;&lt;a href="#fn:7" class="footnote-ref" role="doc-noteref"&gt;7&lt;/a&gt;&lt;/sup&gt; This
uplift was achieved by optimizing pip&amp;rsquo;s &lt;code&gt;importlib&lt;/code&gt; based metadata backend to read
package&lt;sup id="fnref:8"&gt;&lt;a href="#fn:8" class="footnote-ref" role="doc-noteref"&gt;8&lt;/a&gt;&lt;/sup&gt; names and versions from the installed metadata directory
names.&lt;sup id="fnref:9"&gt;&lt;a href="#fn:9" class="footnote-ref" role="doc-noteref"&gt;9&lt;/a&gt;&lt;/sup&gt; Previously, pip would read the name and version from the
&lt;a href="https://packaging.python.org/en/latest/specifications/recording-installed-packages/#the-metadata-file"target="_blank" rel="noopener"&gt;&lt;code&gt;METADATA&lt;/code&gt; file&lt;/a&gt;, which is slow as it involves an extra file read and invoking an email
header parser.&lt;/p&gt;
&lt;h3&gt;Other optimizations&lt;span class="hx:absolute hx:-mt-20" id="other-optimizations"&gt;&lt;/span&gt;
&lt;a href="#other-optimizations" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;dl&gt;
&lt;dt&gt;Faster downloads &lt;em&gt;&lt;a href="https://github.com/pypa/pip/pull/12810"target="_blank" rel="noopener"&gt;PR #12810&lt;/a&gt;&lt;/em&gt; &lt;em&gt;@morotti&lt;/em&gt;&lt;/dt&gt;
&lt;dd&gt;Source distributions and wheels are downloaded in 256 KiB chunks instead of 10 KiB
chunks, which significantly reduces overhead and enables faster download speeds. A
sizable &lt;em&gt;chunk&lt;/em&gt; (🥁) of the performance improvement comes from reducing the number of
updates of the download progress bar.&lt;/dd&gt;
&lt;dt&gt;Faster dependency resolution &lt;em&gt;&lt;a href="https://github.com/pypa/pip/pull/12663"target="_blank" rel="noopener"&gt;PR #12663&lt;/a&gt;&lt;/em&gt; &lt;em&gt;@notatallshaw&lt;/em&gt;&lt;/dt&gt;
&lt;dd&gt;&amp;hellip; of highly complex or pathological dependency trees. As pip&amp;rsquo;s dependency resolver
explores a dependency tree, it has to parse requirements (e.g. &lt;code&gt;pip==24.2&lt;/code&gt;) into a
format it understands. In complex trees, the same requirements are often encountered
many, &lt;em&gt;many&lt;/em&gt; times. There was &lt;em&gt;some&lt;/em&gt; caching in-place to minimize redundant work, but
now requirements are consistently cached.&lt;/dd&gt;
&lt;dt&gt;Faster installation &lt;em&gt;&lt;a href="https://github.com/pypa/pip/pull/12803"target="_blank" rel="noopener"&gt;PR #12803&lt;/a&gt;&lt;/em&gt; &lt;em&gt;@morotti&lt;/em&gt;&lt;/dt&gt;
&lt;dd&gt;Wheels, &lt;a href="https://packaging.python.org/en/latest/specifications/binary-distribution-format/"target="_blank" rel="noopener"&gt;which are really zip archives&lt;/a&gt;, are extracted using a larger block size (either
1MiB or the file size if it&amp;rsquo;s smaller than a MiB). Also, the zip decompressor is no
longer invoked while copying empty files, eliminating pointless CPU cycles for files
like &lt;code&gt;__init__.py&lt;/code&gt;.&lt;/dd&gt;
&lt;dt&gt;Faster reinstalls &lt;em&gt;&lt;a href="https://github.com/pypa/pip/pull/12755"target="_blank" rel="noopener"&gt;PR #12755&lt;/a&gt;&lt;/em&gt; &lt;em&gt;yours truly!&lt;/em&gt;&lt;/dt&gt;
&lt;dd&gt;The full list of platform compatibility tags is now only generated once and reused
across all look-ups of the wheel cache, speeding up cached reinstalls.&lt;/dd&gt;
&lt;/dl&gt;
&lt;h2&gt;&lt;code&gt;pip check&lt;/code&gt; just got a bit stricter&lt;span class="hx:absolute hx:-mt-20" id="pip-check-just-got-a-bit-stricter"&gt;&lt;/span&gt;
&lt;a href="#pip-check-just-got-a-bit-stricter" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;The &lt;code&gt;check&lt;/code&gt; pip command now verifies whether installed packages are declared to support
the current platform, issuing a warning if a package is incompatible.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;pip check
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;catboost 1.1.1 is not supported on this platform
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ninja 1.11.1.1 is not supported on this platform
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;xgboost 1.6.1 is not supported on this platform
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;frozendict 2.3.8 is not supported on this platform
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Under the hood, for every package, pip reads the &lt;code&gt;WHEEL&lt;/code&gt; metadata file that&amp;rsquo;s installed
alongside the package, and checks whether at least one of the compatibility tags is
supported on the platform.&lt;/p&gt;
&lt;p&gt;For example, this is the &lt;code&gt;WHEEL&lt;/code&gt; file from pip 24.2&amp;rsquo;s wheel:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# pip-24.2.dist-info/WHEEL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Wheel-Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Generator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;setuptools (71.1.0)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Root-Is-Purelib&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;py3-none-any&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;py3-none-any&lt;/code&gt; is a platform compatibility tag. What&amp;rsquo;s a compatibility tag?
&lt;a href="https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/"target="_blank" rel="noopener"&gt;They&amp;rsquo;re essentially markers for what systems a package supports.&lt;/a&gt; You can
think of them like languages. They include information on the architecture, Python ABI,
and Python version. Every system has a set of compatibility tags it supports. If a package
and the system it&amp;rsquo;s being installed to share a common tag—or with the analogy, &lt;em&gt;speak a
shared language&lt;/em&gt;—the package is considered compatible.&lt;/p&gt;
&lt;p&gt;In this case, &lt;code&gt;py3-none-any&lt;/code&gt; is code for &amp;ldquo;any Python 3 version&amp;rdquo;, &amp;ldquo;any Python ABI&amp;rdquo;, and
&amp;ldquo;any architecture.&amp;rdquo; This is supported by any modern Python installation, thus this wheel
would be almost always eligible for installation (although it may be rejected for other
reasons, like &lt;code&gt;requires-python&lt;/code&gt; restrictions).&lt;/p&gt;
&lt;p&gt;Now, &lt;code&gt;pip install&lt;/code&gt; already uses compatibility tags to ensure it only installs compatible
packages. This extra check in &lt;code&gt;pip check&lt;/code&gt; is designed to surface packages that were
&lt;strong&gt;compatible at the time of installation, but are no longer supported due to a system
upgrade&lt;/strong&gt;. This is possible, especially as
&lt;a href="https://github.com/pypa/pip/issues/11054"target="_blank" rel="noopener"&gt;Apple transitions from the &lt;code&gt;x86-64&lt;/code&gt; architecture (Intel) to &lt;code&gt;arm64&lt;/code&gt; (Apple Silicon)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;(Un)fortunately, this has had the side-effect of
&lt;a href="https://github.com/pypa/pip/issues/12884"target="_blank" rel="noopener"&gt;revealing numerous packages with inaccurate or outright malformed metadata, leading to false positives&lt;/a&gt;.
This is annoying, but regarded as an intentional side-effect as the pip project has been
moving towards enforcing standards compliance so pip follows the standards instead of de
facto writing them.&lt;sup id="fnref:10"&gt;&lt;a href="#fn:10" class="footnote-ref" role="doc-noteref"&gt;10&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;This was &lt;a href="https://github.com/pypa/pip/pull/11088"target="_blank" rel="noopener"&gt;kindly contributed by @q0w&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Oh yeah, &lt;code&gt;--require-virtualenv&lt;/code&gt; is a thing&lt;span class="hx:absolute hx:-mt-20" id="oh-yeah---require-virtualenv-is-a-thing"&gt;&lt;/span&gt;
&lt;a href="#oh-yeah---require-virtualenv-is-a-thing" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;You can configure pip to only function when running in an activated virtual environment
via the &lt;code&gt;--require-virtualenv&lt;/code&gt; flag. You can set this via a system-side or user-side
configuration file; and it&amp;rsquo;s great for ensuring you don&amp;rsquo;t scribble all over your system or
user Python environment.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;pip install package-that-shouldnt-be-installed-system-wide
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ERROR: Could not find an activated virtualenv (required).
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Anyway, it&amp;rsquo;s &lt;em&gt;supposed&lt;/em&gt; to only apply to commands &lt;strong&gt;that modify the environment&lt;/strong&gt;, like
&lt;code&gt;pip install&lt;/code&gt; or &lt;code&gt;pip uninstall&lt;/code&gt;. Read-only commands, such as &lt;code&gt;pip list&lt;/code&gt;, are unaffected
by the flag and will function even if a virtual environment is not activated.&lt;/p&gt;
&lt;p&gt;This release extends this QoL feature to &lt;code&gt;pip check&lt;/code&gt; and &lt;code&gt;pip index&lt;/code&gt;. You can now discover
unsupported packages whenever you want. &lt;a href="https://github.com/pypa/pip/pull/12842"target="_blank" rel="noopener"&gt;Thanks @branchvincent&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Summary&lt;span class="hx:absolute hx:-mt-20" id="summary"&gt;&lt;/span&gt;
&lt;a href="#summary" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;pip 24.2 delivers considerable performance and QoL improvements. It also continues the pip
project&amp;rsquo;s goal of following and enforcing the use of and compliance with the
interoperatability standards that have shaped modern Python packaging.&lt;/p&gt;
&lt;p&gt;I realize that this post is long, but this release contained some cool changes and I
believed they deserved more attention than a brief entry in the changelog. I make no
promises that I&amp;rsquo;ll continue this series for future pip releases, but this sure was fun to
write!&lt;/p&gt;
&lt;p&gt;Finally, I&amp;rsquo;d like to thank Bartosz Sokorski, Bradley Reynolds, Hugo van Kemenade, and Paul
Moore for reviewing this post in draft form and suggesting improvements. Any mistakes are
of course my own.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Poetry is the all encompassing user-facing tool for packaging and dependency
management, while &lt;code&gt;poetry-core&lt;/code&gt; is its backend. The same nitpick exists for all other
&amp;ldquo;backends&amp;rdquo; I mention.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;In reality, pip calls other hooks as well to query additional build dependencies and
generate metadata, but this isn&amp;rsquo;t the post where I explain pip&amp;rsquo;s entire PEP 517
implementation.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;&lt;code&gt;--no-build-isolation&lt;/code&gt; may be needed if the project has build-time requirements beyond
setuptools and wheel. By passing this flag, you are responsible for making sure your
environment already has the required dependencies to build your package.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;As of writing, the TL;DR is that strict editables are implemented as a tree of file
links to the original source files in an auxiliary directory which is then added to
&lt;code&gt;sys.path&lt;/code&gt;, while legacy &amp;ldquo;compat mode&amp;rdquo; editables are implemented by placing the entire
root of your package on &lt;code&gt;sys.path&lt;/code&gt;. The new method is designed to ensure only files
present at installation are exposed, mimicking a normal installation. It&amp;rsquo;s rather neat
that setuptools has support for all three methods described for implementing an
editable install in PEP 660.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;This &lt;a href="https://github.com/pypa/pip/issues/1680"target="_blank" rel="noopener"&gt;was recognized as a problem in 2014&lt;/a&gt;, and pip gained the ability to
&lt;a href="https://github.com/pypa/pip/pull/1866"target="_blank" rel="noopener"&gt;use system certificates on Unix in 2015&lt;/a&gt;, but this change was later
reverted because it turned out
&lt;a href="https://github.com/pypa/pip/commit/c77d4ab55ed412a3b72d0b73f504e8ddec918683"target="_blank" rel="noopener"&gt;none of the major distributions had a functional OpenSSL configuration at the time&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:6"&gt;
&lt;p&gt;pip continues to use truststore in tandem with certifi. This is necessary for
platforms that do not support truststore, but also provides a fallback if the system
trust store is broken or otherwise inaccessible for some reason. There are currently
no plans for pip to switch to exclusively use truststore.&amp;#160;&lt;a href="#fnref:6" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:7"&gt;
&lt;p&gt;Also, it&amp;rsquo;s worth mentioning that 3/4 of the 200ms is taken by Python&amp;rsquo;s own startup
overhead and importing everything &lt;code&gt;pip list&lt;/code&gt; needs.&amp;#160;&lt;a href="#fnref:7" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:8"&gt;
&lt;p&gt;OK, so technically I should be saying &amp;ldquo;distribution&amp;rdquo; to avoid ambiguity as the term
&amp;ldquo;package&amp;rdquo; means something different under the Python import system, but distribution
sounds weird which is why I&amp;rsquo;m saying &amp;ldquo;package&amp;rdquo; still. I&amp;rsquo;m horribly inconsistent about
this though.&amp;#160;&lt;a href="#fnref:8" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:9"&gt;
&lt;p&gt;If you dig into your &lt;code&gt;site-packages&lt;/code&gt; directory, you&amp;rsquo;ll notice that there is a bunch of
&lt;code&gt;&amp;lt;name&amp;gt;-&amp;lt;version&amp;gt;.dist-info&lt;/code&gt; directories. These contain package metadata, and there is
one for each package installed. They are included in wheels and are copied during
installation.&amp;#160;&lt;a href="#fnref:9" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:10"&gt;
&lt;p&gt;Historically, as pip was the only installer in town, various bits of pip&amp;rsquo;s design or
behaviour has been become de facto standards. Even if there is a standard, &amp;ldquo;pip
supports X&amp;rdquo; or &amp;ldquo;pip does X&amp;rdquo; can be regularly seen in the issue trackers of other
packaging tools. While this is expected and unavoidable, it&amp;rsquo;s unideal. We don&amp;rsquo;t want
to be in the business of telling what everyone else should be doing, especially as pip
is old and has quirks and design flaws that frankly shouldn&amp;rsquo;t exist, let alone be
copied by other tools. By gradually enforcing standards compliance, the Python
packaging ecosystem becomes more and more defined by &lt;em&gt;common specifications&lt;/em&gt;, allowing
other packaging tools to function without reimplementing a bunch of pip bugs or
whatever.&amp;#160;&lt;a href="#fnref:10" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>What's new in Black 23.1.0</title><link>https://sichard.ca/blog/2023/01/black-23.1.0/</link><pubDate>Tue, 31 Jan 2023 00:00:00 -0500</pubDate><guid>https://sichard.ca/blog/2023/01/black-23.1.0/</guid><description>
&lt;p&gt;Today we released &lt;a href="https://github.com/psf/black/releases/tag/23.1.0"target="_blank" rel="noopener"&gt;Black 23.1.0&lt;/a&gt; 🎉.&lt;/p&gt;
&lt;p&gt;While Black does not follow SemVar, it is indeed a major release as it ships with the
&lt;strong&gt;2023 stable style&lt;/strong&gt;. Many changes made to the &lt;strong&gt;preview&lt;/strong&gt; style over the past year have
been &lt;em&gt;finally&lt;/em&gt; promoted to the &lt;strong&gt;stable&lt;/strong&gt; style.&lt;/p&gt;
&lt;p&gt;You can install it by running this command:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m pip install &lt;span class="nv"&gt;black&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;23.1.0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Below I&amp;rsquo;ll cover the important changes worth mentioning. If you’d like the full changelog
for 23.1.0, please refer to the link above.&lt;/p&gt;
&lt;h2&gt;Code style&lt;span class="hx:absolute hx:-mt-20" id="code-style"&gt;&lt;/span&gt;
&lt;a href="#code-style" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3&gt;The 2023 stable style&lt;span class="hx:absolute hx:-mt-20" id="the-2023-stable-style"&gt;&lt;/span&gt;
&lt;a href="#the-2023-stable-style" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;We didn&amp;rsquo;t make any major changes to the 2023 stable style draft since 23.1a1.&lt;/p&gt;
&lt;p&gt;So for the major parts of the new stable style,
&lt;a href="https://sichard.ca/blog/2022/12/black-23.1a1/"&gt;&lt;strong&gt;please refer to the 23.1a1 post&lt;/strong&gt;&lt;/a&gt;. Everything under the
&amp;ldquo;promoted changes&amp;rdquo; header were promoted &lt;em&gt;officially&lt;/em&gt; in 23.1.0. The TL;DR is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Improved empty line handling (mostly removing unnecessary ones)&lt;/li&gt;
&lt;li&gt;Removal of redundant parentheses in several contexts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We did also fix quite a few bugs and crashes, including these ones reporting by users
testing out 23.1a1:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/psf/black/issues/3472"target="_blank" rel="noopener"&gt;Preview style: Crash on walrus in with statement (#3472)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/psf/black/issues/3454"target="_blank" rel="noopener"&gt;Extra newlines added before function with fmt skip on decorator (#3454)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/psf/black/issues/3419"target="_blank" rel="noopener"&gt;Preview style incorrectly removes parens from walrus in annotation (#3419)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the rest, please refer to the &lt;a href="https://github.com/psf/black/releases/tag/23.1.0"target="_blank" rel="noopener"&gt;changelog&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;The 2023 preview style&lt;span class="hx:absolute hx:-mt-20" id="the-2023-preview-style"&gt;&lt;/span&gt;
&lt;a href="#the-2023-preview-style" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;h4&gt;Conditional expressions are parenthesized if needed now&lt;span class="hx:absolute hx:-mt-20" id="conditional-expressions-are-parenthesized-if-needed-now"&gt;&lt;/span&gt;
&lt;a href="#conditional-expressions-are-parenthesized-if-needed-now" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Issue&lt;/strong&gt;: &lt;a href="https://github.com/psf/black/issues/2248"target="_blank" rel="noopener"&gt;#2248&lt;/a&gt; ~ &lt;strong&gt;PR&lt;/strong&gt;: &lt;a href="https://github.com/psf/black/issues/2278"target="_blank" rel="noopener"&gt;#2278&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If a conditional expression is too long, it will be wrapped in parentheses instead of
whatever nonsense Black did before.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll just let this diff speak for itself :)&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;--- pyanalyze/typeshed.py 2021-05-30 03:06:13.755715 +0000
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+++ pyanalyze/typeshed.py 2021-05-30 03:06:32.534930 +0000
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -448,13 +448,13 @@
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; arg = arg.replace(kind=SigParameter.POSITIONAL_OR_KEYWORD)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cleaned_arguments.append(arg)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return Signature.make(
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cleaned_arguments,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; callable=obj,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- return_annotation=GenericValue(Awaitable, [return_value])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- if is_async_fn
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- else return_value,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ return_annotation=(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ GenericValue(Awaitable, [return_value]) if is_async_fn else return_value
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ ),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; def _parse_param_list(
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; self,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; args: Iterable[ast3.arg],
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;There are certain cases where it might be overkill, like this one, but overall, I consider
it a major improvement.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; normalized = [
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- (source, source)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- if source == &amp;#34;-&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- else (normalize_path_maybe_ignore(Path(source), root), source)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ (
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ (source, source)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ if source == &amp;#34;-&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ else (normalize_path_maybe_ignore(Path(source), root), source)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ )
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; for source in src
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4&gt;Wrap multiple context managers in parentheses if targeting Python 3.9+&lt;span class="hx:absolute hx:-mt-20" id="wrap-multiple-context-managers-in-parentheses-if-targeting-python-39"&gt;&lt;/span&gt;
&lt;a href="#wrap-multiple-context-managers-in-parentheses-if-targeting-python-39" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Issues&lt;/strong&gt;: &lt;a href="https://github.com/psf/black/issues/3486"target="_blank" rel="noopener"&gt;#3486&lt;/a&gt; + &lt;a href="https://github.com/psf/black/issues/664"target="_blank" rel="noopener"&gt;#664&lt;/a&gt; ~ &lt;strong&gt;PR&lt;/strong&gt;: &lt;a href="https://github.com/psf/black/pull/3489"target="_blank" rel="noopener"&gt;#3489&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Since Python 3.9&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;, you can break long with statements using parentheses. Thanks to work
done by &lt;a href="https://github.com/yilei"target="_blank" rel="noopener"&gt;@yilei&lt;/a&gt;, Black will now use this style as long as the lowest targeted version is
3.9.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;make_context_manager1&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;cm1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;make_context_manager2&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;cm2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;make_context_manager3&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;cm3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;make_context_manager4&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;cm4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;pass&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; unchanged!!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;make_context_manager1&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;cm1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;make_context_manager2&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;cm2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;make_context_manager3&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;cm3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;make_context_manager4&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;cm4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;pass&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Black still doesn&amp;rsquo;t have a good story for formatting such with statements when Python
&amp;lt;3.9 support is requested. All of the gory details can be found in issue &lt;a href="https://github.com/psf/black/issues/664"target="_blank" rel="noopener"&gt;#664&lt;/a&gt;. (&lt;em&gt;The
summary is that no one has stepped up to implement the proposed style.&lt;/em&gt;)&lt;/p&gt;
&lt;h2&gt;Packaging&lt;span class="hx:absolute hx:-mt-20" id="packaging"&gt;&lt;/span&gt;
&lt;a href="#packaging" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3&gt;mypyc wheels are available for all platforms (once again)&lt;span class="hx:absolute hx:-mt-20" id="mypyc-wheels-are-available-for-all-platforms-once-again"&gt;&lt;/span&gt;
&lt;a href="#mypyc-wheels-are-available-for-all-platforms-once-again" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Long story short, recent versions of packaging and hatchling interacted with ways that led
to errors when trying to build macOS mypyc wheels. The essence is that the
&lt;a href="https://github.com/psf/black/issues/3312"target="_blank" rel="noopener"&gt;wrong platform tag was being chosen which made the wheels uninstallable in certain situations&lt;/a&gt;
(including on the same machine that built wheels &amp;gt;.&amp;lt;).&lt;/p&gt;
&lt;p&gt;Anyway that&amp;rsquo;s all fixed now and 23.1.0 ships with macOS wheels, bringing us back to the
full set of compiled wheels.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This affected the 22.10.0, 22.12.0, and 23.1a1 releases.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;mypycified Black can be now built on armv7&lt;span class="hx:absolute hx:-mt-20" id="mypycified-black-can-be-now-built-on-armv7"&gt;&lt;/span&gt;
&lt;a href="#mypycified-black-can-be-now-built-on-armv7" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;We upgraded mypy/c from &lt;code&gt;0.971&lt;/code&gt; which &lt;code&gt;0.991&lt;/code&gt; which allows mypycified Black to be built on
armv7.&lt;/p&gt;
&lt;p&gt;Conveniently, this also fixes some segfaults caused by mypyc:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/psf/black/issues/2845"target="_blank" rel="noopener"&gt;Segfault with black 22.1.0 (#2845)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/psf/black/issues/2867"target="_blank" rel="noopener"&gt;black running on address sanitized Python interpreter crashes (#2867)&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;same underlying issue as the one above&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Output&lt;span class="hx:absolute hx:-mt-20" id="output"&gt;&lt;/span&gt;
&lt;a href="#output" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3&gt;No longer incorrectly claim that symbolic links exist when formatting from outside a project&lt;span class="hx:absolute hx:-mt-20" id="no-longer-incorrectly-claim-that-symbolic-links-exist-when-formatting-from-outside-a-project"&gt;&lt;/span&gt;
&lt;a href="#no-longer-incorrectly-claim-that-symbolic-links-exist-when-formatting-from-outside-a-project" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Issue&lt;/strong&gt;: &lt;a href="https://github.com/psf/black/issues/3384"target="_blank" rel="noopener"&gt;#3384&lt;/a&gt; ~ &lt;strong&gt;PR&lt;/strong&gt;: &lt;a href="https://github.com/psf/black/pull/3385"target="_blank" rel="noopener"&gt;#3385&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When trying to format a project from the outside, the verbose output shows says that there
are symbolic links that points outside of the project, but displays the wrong project
path. This behavior was triggered when Black is executed from outside the project&amp;rsquo;s root.&lt;/p&gt;
&lt;p&gt;Consider the following tree:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;.
└── home/
└── project/
├── .git
└── dir/
└── main.py&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;When trying to format a folder from home, this is the output:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; black ./project --check --verbose
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Identified `/path/to/home/project` as project root containing a .git directory.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Sources to be formatted: &amp;#34;.&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;project/.git ignored: is a symbolic link that points outside /path/to/home/project/project
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;project/.git ignored: matches the --exclude regular expression
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;project/dir ignored: is a symbolic link that points outside /path/to/home/project/project
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;project/dir/file.py ignored: is a symbolic link that points outside /path/to/home/project/project
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;would reformat project/dir/file.py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Oh no! 💥 💔 💥
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;1 file would be reformatted.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Notice that the message
&lt;code&gt;$FILEPATH ignored: is a symbolic link that points outside $PROJECTPATH&lt;/code&gt; displays a wrong
$PROJECTPATH (should be &lt;code&gt;/path/to/home/project/&lt;/code&gt; instead of
&lt;code&gt;/path/to/home/project/project&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Anyway, Black will no longer emit these superfluous and incorrect messages. All thanks
goes to &lt;a href="https://github.com/aaossa"target="_blank" rel="noopener"&gt;@aaossa&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Verbose output now shows the full loaded configuration&lt;span class="hx:absolute hx:-mt-20" id="verbose-output-now-shows-the-full-loaded-configuration"&gt;&lt;/span&gt;
&lt;a href="#verbose-output-now-shows-the-full-loaded-configuration" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Issue&lt;/strong&gt;: &lt;a href="https://github.com/psf/black/issues/3386"target="_blank" rel="noopener"&gt;#3386&lt;/a&gt; ~ &lt;strong&gt;PR&lt;/strong&gt;: &lt;a href="https://github.com/psf/black/pull/3392"target="_blank" rel="noopener"&gt;#3392&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&amp;rsquo;s easier to just copy and paste part of the issue to explain this feature:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Sometimes when dealing with multiple environment settings, IDEs, and pyproject.toml
files etc. etc. is beneficial for a project team to have a confirmation about the actual
applied black-settings (&amp;ldquo;line length&amp;rdquo;) in the CI CD pipeline.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;black -v&lt;/code&gt; will show a line like this: &lt;code&gt;Using configuration from project root.&lt;/code&gt;.
However, it doesn&amp;rsquo;t tell us what the settings are [&amp;hellip;]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;hellip; so we changed Black to show the loaded configuration if &lt;code&gt;-v&lt;/code&gt; / &lt;code&gt;--verbose&lt;/code&gt; is passed.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; black src/black/parsing.py --verbose
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Identified `/home/ichard26/programming/oss/black` as project root containing a .git directory.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Sources to be formatted: &amp;#34;src/black/parsing.py&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Using configuration from project root.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;line_length: 88
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;target_version: [&amp;#39;py37&amp;#39;, &amp;#39;py38&amp;#39;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;include: \.pyi?$
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;extend_exclude: /(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; # The following are specific to Black, you probably don&amp;#39;t want those.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; | blib2to3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; | tests/data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; | profiling
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;)/
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;preview: True
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;src/black/parsing.py already well formatted, good job.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;All done! ✨ 🍰 ✨
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;1 file left unchanged.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Some cleaning up can definitely be done to the verbose output, but at least it&amp;rsquo;s there
now.&lt;/p&gt;
&lt;h2&gt;Configuration&lt;span class="hx:absolute hx:-mt-20" id="configuration"&gt;&lt;/span&gt;
&lt;a href="#configuration" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3&gt;Better default for &amp;ndash;target-version if PEP 621 python-requires is available&lt;span class="hx:absolute hx:-mt-20" id="better-default-for-target-version-if-pep-621-python-requires-is-available"&gt;&lt;/span&gt;
&lt;a href="#better-default-for-target-version-if-pep-621-python-requires-is-available" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Issue&lt;/strong&gt;: &lt;a href="https://github.com/psf/black/issues/3124"target="_blank" rel="noopener"&gt;#3124&lt;/a&gt; ~ &lt;strong&gt;PR&lt;/strong&gt;: &lt;a href="https://github.com/psf/black/pull/3219"target="_blank" rel="noopener"&gt;#3219&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Black will now infer a default set of target versions using the &lt;code&gt;project.requires-python&lt;/code&gt;
field in &lt;code&gt;pyproject.toml&lt;/code&gt; if possible.&lt;/p&gt;
&lt;p&gt;For example, these are the inferred default for &lt;code&gt;--target-version&lt;/code&gt; for three different
&lt;code&gt;requires-python&lt;/code&gt; values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;3.8.5&lt;/code&gt; becomes &lt;code&gt;[&amp;quot;py38&amp;quot;]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;gt;3.6,&amp;lt;3.11&lt;/code&gt; becomes &lt;code&gt;[&amp;quot;py37&amp;quot;, &amp;quot;py38&amp;quot;, &amp;quot;py39&amp;quot;, &amp;quot;py310&amp;quot;]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;3.7&lt;/code&gt; becomes &lt;code&gt;[&amp;quot;py33&amp;quot;, &amp;quot;py34&amp;quot;, &amp;quot;py35&amp;quot;, &amp;quot;py36&amp;quot;]&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Python 3.3 is the minimal supported version for Black&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you&amp;rsquo;re curious what Black infers for your project, make sure to comment out any
&lt;code&gt;--target-version&lt;/code&gt; configuration you have set and run Black with &lt;code&gt;--verbose&lt;/code&gt;. Thanks to
the change mentioned previously, there should be a line showing the (inferred)
target-version config value.&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;--target-version&lt;/code&gt; is explicitly configured on the command line or in &lt;code&gt;pyproject.toml&lt;/code&gt;,
it will trump the inferred configuration. And if Black encounters any error inferring a
better default, it will simply fallback to per-file autodetection (like it did before).&lt;/p&gt;
&lt;p&gt;To make this work, &lt;strong&gt;Black now requires &lt;a href="https://pypi.org/project/packaging/"target="_blank" rel="noopener"&gt;packaging&lt;/a&gt;&lt;/strong&gt; (version 22 and up). It&amp;rsquo;s a tiny
dependency and often installed with other development tools so it shouldn&amp;rsquo;t be a big deal.&lt;/p&gt;
&lt;p&gt;In my opinion, the general hope is that the vast majority of projects will &lt;em&gt;eventually&lt;/em&gt;
use &lt;strong&gt;Black&amp;rsquo;s inference capabilities instead of setting &lt;code&gt;--target-version&lt;/code&gt; separately&lt;/strong&gt;.
It&amp;rsquo;s less work (no need to update it!) and should be less error prone.&lt;/p&gt;
&lt;p&gt;At the bare minimum, &lt;strong&gt;new projects shouldn&amp;rsquo;t have to configure &lt;code&gt;--target-version&lt;/code&gt;
anymore&lt;/strong&gt; as long as they define their project metadata
&lt;a href="https://peps.python.org/pep-0621/"target="_blank" rel="noopener"&gt;in the standardized way&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This was contributed by the lovely &lt;a href="https://github.com/stinodego"target="_blank" rel="noopener"&gt;@stinodego&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;A few final words&lt;span class="hx:absolute hx:-mt-20" id="a-few-final-words"&gt;&lt;/span&gt;
&lt;a href="#a-few-final-words" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;We tried to do &lt;a href="https://github.com/psf/black/issues/3407"target="_blank" rel="noopener"&gt;some community outreach&lt;/a&gt; in an attempt to iron out issues &lt;em&gt;before&lt;/em&gt;
cutting 23.1.0 and make sure the 2023 stable style wouldn&amp;rsquo;t be a failure. We got some
helpful feedback, but I realize we could&amp;rsquo;ve done more.&lt;/p&gt;
&lt;p&gt;If you have any feedback for this release or suggestions for next time, &lt;strong&gt;please let us
know!&lt;/strong&gt; Feel free to open an issue, &lt;a href="https://sichard.ca/about/#contact-information"&gt;email me&lt;/a&gt;, &lt;a href="https://discord.gg/RtVdv86PrH"target="_blank" rel="noopener"&gt;message us on Discord&lt;/a&gt;, whatever.&lt;/p&gt;
&lt;p&gt;Anyway, thanks for reading this. On behalf of the maintainer team, I hope you enjoy the
new release!&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Well officially speaking, this is only supported starting in Python 3.10 and higher,
but GvR and co. might have snuck it into 3.9 :P&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Black 23.1a1 - please help us test the 2023 stable style!</title><link>https://sichard.ca/blog/2022/12/black-23.1a1/</link><pubDate>Sun, 18 Dec 2022 00:00:00 -0500</pubDate><guid>https://sichard.ca/blog/2022/12/black-23.1a1/</guid><description>
&lt;p&gt;As the post summary says, earlier today &lt;a href="https://github.com/psf/black/releases/tag/23.1a1"target="_blank" rel="noopener"&gt;we released Black 23.1a1&lt;/a&gt;. You might
be wondering why we released an alpha&amp;hellip; it&amp;rsquo;s because the upcoming Black 23.1.0 release
will see many &lt;strong&gt;preview&lt;/strong&gt; code style changes promoted to the &lt;strong&gt;stable&lt;/strong&gt; style.&lt;/p&gt;
&lt;p&gt;In other words, &lt;strong&gt;Black 23.1.0 will format your code differently out of the box&lt;/strong&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you&amp;rsquo;re surprised we&amp;rsquo;re doing this, our &lt;a href="https://black.readthedocs.io/en/stable/the_black_code_style/index.html#stability-policy"target="_blank" rel="noopener"&gt;stability policy&lt;/a&gt; allows us
to update the stable style in the new year. This minimises disruption while keeping the
door for improvements open.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;23.1a1 contains a draft of the 2023 stable style that we, the maintainers, initially
decided on.&lt;/p&gt;
&lt;p&gt;However, &lt;strong&gt;we need your help in finalizing what goes into the 2023 stable style&lt;/strong&gt;. If you
can, please try 23.1a1 out on your codebase(s). You can install it by running this
command:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m pip install &lt;span class="nv"&gt;black&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;23.1a1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;If you have any feedback, concerns, or run into any issues, please drop us a comment
&lt;a href="https://github.com/psf/black/issues/3407"target="_blank" rel="noopener"&gt;in this issue&lt;/a&gt;&lt;/strong&gt; (&lt;a href="https://github.com/psf/black/issues/3407"target="_blank" rel="noopener"&gt;https://github.com/psf/black/issues/3407&lt;/a&gt;).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Please read the start of the issue description before commenting. It will make
the lives of everyone easier.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What follows is brief rundown of all of the changes in the preview style that were
promoted to stable in 23.1a1.&lt;/p&gt;
&lt;h2&gt;Promoted changes&lt;span class="hx:absolute hx:-mt-20" id="promoted-changes"&gt;&lt;/span&gt;
&lt;a href="#promoted-changes" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3&gt;Bugfixes&lt;span class="hx:absolute hx:-mt-20" id="bugfixes"&gt;&lt;/span&gt;
&lt;a href="#bugfixes" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;h4&gt;&lt;code&gt;--skip-string-normalization&lt;/code&gt; now prevents docstring prefixes from being normalized&lt;span class="hx:absolute hx:-mt-20" id="--skip-string-normalization-now-prevents-docstring-prefixes-from-being-normalized"&gt;&lt;/span&gt;
&lt;a href="#--skip-string-normalization-now-prevents-docstring-prefixes-from-being-normalized" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;!-- 1 --&gt;
&lt;p&gt;&lt;code&gt;-S&lt;/code&gt; / &lt;code&gt;--skip-string-normalization&lt;/code&gt; stops Black from normalizing string prefixes and
quotes, except when it doesn&amp;rsquo;t&amp;hellip;&lt;/p&gt;
&lt;p&gt;Sometime ago I accidentally added a bug where Black would normalize docstring prefixes
even if you told it not to, leading to this:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; def add(a, b):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- F&amp;#34;&amp;#34;&amp;#34;Add two numbers and return the result.&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ f&amp;#34;&amp;#34;&amp;#34;Add two numbers and return the result.&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return a + b
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/psf/black/pull/3168"target="_blank" rel="noopener"&gt;This has been fixed in the preview style since 22.8.0&lt;/a&gt; (leaving this example
unchanged).&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;--skip-magic-trailing-comma&lt;/code&gt; now removes useless trailing commas in subscripts&lt;span class="hx:absolute hx:-mt-20" id="--skip-magic-trailing-comma-now-removes-useless-trailing-commas-in-subscripts"&gt;&lt;/span&gt;
&lt;a href="#--skip-magic-trailing-comma-now-removes-useless-trailing-commas-in-subscripts" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;!-- 2 --&gt;
&lt;p&gt;Black has the concept of &amp;ldquo;magic trailing commas.&amp;rdquo;
&lt;a href="https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#the-magic-trailing-comma"target="_blank" rel="noopener"&gt;You can read about it here&lt;/a&gt;, but in essence, Black treats trailing
commas as a sign that collections, calls, or function signatures should always be
exploded.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# The trailing comma will stop Black from collapsing this collection into one line.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;TRANSLATIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;en_us&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;English (US)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;pl_pl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;polski&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This can be disabled passing the &lt;code&gt;-C&lt;/code&gt; / &lt;code&gt;--skip-magic-trailing-comma&lt;/code&gt; flag.&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;However, Black didn&amp;rsquo;t remove unnecessary trailing commas in all situations. In the
following example, the trailing comma in the function signature was removed as expected,
but not the subscript.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Point = Tuple[int, int,]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-def f(a: Point, b: Point,): ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+def f(a: Point, b: Point): ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/psf/black/pull/3209"target="_blank" rel="noopener"&gt;This was fixed in the preview style since 22.8.0&lt;/a&gt;, which removes both commas as
expected.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-Point = Tuple[int, int,]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+Point = Tuple[int, int]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-def f(a: Point, b: Point,): ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+def f(a: Point, b: Point): ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4&gt;Correctly handle trailing commas that are inside a line&amp;rsquo;s leading non-nested parens&lt;span class="hx:absolute hx:-mt-20" id="correctly-handle-trailing-commas-that-are-inside-a-lines-leading-non-nested-parens"&gt;&lt;/span&gt;
&lt;a href="#correctly-handle-trailing-commas-that-are-inside-a-lines-leading-non-nested-parens" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;!-- 18 --&gt;
&lt;p&gt;There are actually two problems in the &amp;ldquo;before&amp;rdquo; output:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;zero(one,).two(three,).four(five,)&lt;/code&gt; chain should be fully exploded&lt;/li&gt;
&lt;li&gt;The magic trailing comma in the dictionary in &lt;code&gt;refresh_token()&lt;/code&gt; is being ignored&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;one&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;two&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;three&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;four&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;five&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device_family&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;refreshToken&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;sdk&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;token&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;one&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;two&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;three&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;four&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;five&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device_family&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;refreshToken&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;,},&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,)[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;sdk&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;token&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;one&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;two&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;three&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;four&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;five&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device_family&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;refreshToken&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;sdk&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;token&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/psf/black/pull/3370"target="_blank" rel="noopener"&gt;Both these issues have been fixed in the preview style since 22.12.0&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;End quotes that violate the line length limit in multiline docstrings will be moved&lt;span class="hx:absolute hx:-mt-20" id="end-quotes-that-violate-the-line-length-limit-in-multiline-docstrings-will-be-moved"&gt;&lt;/span&gt;
&lt;a href="#end-quotes-that-violate-the-line-length-limit-in-multiline-docstrings-will-be-moved" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;!-- 5 --&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_and_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; Remove punctuation and return cleaned string, in addition to its length in tokens.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;example_2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;Remove punctuation and return cleaned string in addition to its length in tokens.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;pass&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Here, the third line violates the line length limit (90 &amp;gt; 88) because of the end quotes.
Earlier versions of Black would leave this be which wasn&amp;rsquo;t ideal, so now they are moved
onto their own line.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; def process_and_count(text):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- Remove punctuation and return cleaned string, in addition to its length in tokens.&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ Remove punctuation and return cleaned string, in addition to its length in tokens.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ &amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pass
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;However, Black won&amp;rsquo;t move quotes of single line docstrings (such as with &lt;code&gt;example_2()&lt;/code&gt;)
since that would look ugly.&lt;/p&gt;
&lt;p&gt;This was &lt;a href="https://github.com/psf/black/pull/3044"target="_blank" rel="noopener"&gt;added to the preview style in 22.8.0&lt;/a&gt; and
&lt;a href="https://github.com/psf/black/pull/3430"target="_blank" rel="noopener"&gt;then tweaked in 23.1a1&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Better management of parentheses&lt;span class="hx:absolute hx:-mt-20" id="better-management-of-parentheses"&gt;&lt;/span&gt;
&lt;a href="#better-management-of-parentheses" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Here&amp;rsquo;s a set of changes that together improve Black&amp;rsquo;s handling of parentheses in various
situations.&lt;/p&gt;
&lt;p&gt;Out of these five,
&lt;a href="#remove-redundant-outermost-parentheses-in-for-statements"&gt;&amp;ldquo;Remove redundant (outermost) parentheses in for statements&amp;rdquo;&lt;/a&gt;
will probably be the most impactful.&lt;/p&gt;
&lt;!-- 6, 7, 8, 11, 12 --&gt;
&lt;h4&gt;Return type annotations&lt;span class="hx:absolute hx:-mt-20" id="return-type-annotations"&gt;&lt;/span&gt;
&lt;a href="#return-type-annotations" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;foo&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="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;frobnicate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ThisIsTrulyUnreasonablyExtremelyLongClassName&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ThisIsTrulyUnreasonablyExtremelyLongClassName&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;pass&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;foo&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="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;frobnicate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ThisIsTrulyUnreasonablyExtremelyLongClassName&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ThisIsTrulyUnreasonablyExtremelyLongClassName&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;pass&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt; (&lt;a href="https://github.com/psf/black/pull/2990"target="_blank" rel="noopener"&gt;22.6.0+&lt;/a&gt;):&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;foo&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;frobnicate&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ThisIsTrulyUnreasonablyExtremelyLongClassName&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ThisIsTrulyUnreasonablyExtremelyLongClassName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;pass&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4&gt;Remove redundant parentheses around awaited objects&lt;span class="hx:absolute hx:-mt-20" id="remove-redundant-parentheses-around-awaited-objects"&gt;&lt;/span&gt;
&lt;a href="#remove-redundant-parentheses-around-awaited-objects" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;set_of_tasks&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;other_set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; unchanged.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt; (&lt;a href="https://github.com/psf/black/pull/2991"target="_blank" rel="noopener"&gt;22.6.0+&lt;/a&gt;):&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;set_of_tasks&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;other_set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4&gt;Remove redundant parentheses in with statements&lt;span class="hx:absolute hx:-mt-20" id="remove-redundant-parentheses-in-with-statements"&gt;&lt;/span&gt;
&lt;a href="#remove-redundant-parentheses-in-with-statements" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bla.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bla.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bla.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bla.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bla.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;pass&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; unchanged.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt; (&lt;a href="https://github.com/psf/black/pull/2926"target="_blank" rel="noopener"&gt;22.6.0+&lt;/a&gt;):&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bla.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bla.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bla.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bla.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bla.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;pass&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4&gt;Remove redundant parentheses from except clauses&lt;span class="hx:absolute hx:-mt-20" id="remove-redundant-parentheses-from-except-clauses"&gt;&lt;/span&gt;
&lt;a href="#remove-redundant-parentheses-from-except-clauses" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;something&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;AttributeError&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;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; unchanged.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt; (&lt;a href="https://github.com/psf/black/pull/2939"target="_blank" rel="noopener"&gt;22.3.0+&lt;/a&gt;):&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;something&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;AttributeError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4&gt;Remove redundant (outermost) parentheses in for statements&lt;span class="hx:absolute hx:-mt-20" id="remove-redundant-outermost-parentheses-in-for-statements"&gt;&lt;/span&gt;
&lt;a href="#remove-redundant-outermost-parentheses-in-for-statements" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; unchanged.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt; (&lt;a href="https://github.com/psf/black/pull/2945"target="_blank" rel="noopener"&gt;22.3.0+&lt;/a&gt;):&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Unfortunately, nested parentheses are still left untouched.
&lt;a href="https://github.com/psf/black/pull/3243"target="_blank" rel="noopener"&gt;I have an open PR to fix that&lt;/a&gt;, but it isn&amp;rsquo;t ready yet.&lt;/p&gt;
&lt;h3&gt;Remove blank lines after code block open&lt;span class="hx:absolute hx:-mt-20" id="remove-blank-lines-after-code-block-open"&gt;&lt;/span&gt;
&lt;a href="#remove-blank-lines-after-code-block-open" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;!-- 9 --&gt;
&lt;p&gt;Here&amp;rsquo;s another change that is likely to impact your codebase.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: This new feature will be applied to &lt;strong&gt;all code blocks&lt;/strong&gt;: &lt;code&gt;def&lt;/code&gt;, &lt;code&gt;class&lt;/code&gt;, &lt;code&gt;if&lt;/code&gt;,
&lt;code&gt;for&lt;/code&gt; , &lt;code&gt;while&lt;/code&gt;, &lt;code&gt;with&lt;/code&gt;, &lt;code&gt;case&lt;/code&gt; and &lt;code&gt;match&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;All the newlines above me should be deleted!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;No newline above me!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;There is a newline above me, and that&amp;#39;s OK!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;as_tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;All the newlines above me should be deleted!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;No newline above me!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;There is a newline above me, and that&amp;#39;s OK!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;as_tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt; (&lt;a href="https://github.com/psf/black/pull/3035"target="_blank" rel="noopener"&gt;22.6.0+&lt;/a&gt;):&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;All the newlines above me should be deleted!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;No newline above me!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;There is a newline above me, and that&amp;#39;s OK!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;as_tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3&gt;Ignore magic trailing comma for single-element subscripts&lt;span class="hx:absolute hx:-mt-20" id="ignore-magic-trailing-comma-for-single-element-subscripts"&gt;&lt;/span&gt;
&lt;a href="#ignore-magic-trailing-comma-for-single-element-subscripts" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;!-- 13 --&gt;
&lt;p&gt;Black exempts single-element tuple literals from the usual handling of magic trailing
commas: &lt;code&gt;(1,)&lt;/code&gt; will not have a newline added (as would happen for lists, sets, etc.).&lt;/p&gt;
&lt;p&gt;However, if you wrote &lt;code&gt;tuple[int,]&lt;/code&gt; (to make it more visually distinctive to &lt;code&gt;list[int]&lt;/code&gt;),
Black would explode it, which doesn&amp;rsquo;t look great.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;shape&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;shape&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt; (&lt;a href="https://github.com/psf/black/pull/2942"target="_blank" rel="noopener"&gt;22.3.0+&lt;/a&gt;): unchanged.&lt;/p&gt;
&lt;h3&gt;Enforce empty lines before classes/functions with sticky leading comments&lt;span class="hx:absolute hx:-mt-20" id="enforce-empty-lines-before-classesfunctions-with-sticky-leading-comments"&gt;&lt;/span&gt;
&lt;a href="#enforce-empty-lines-before-classesfunctions-with-sticky-leading-comments" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;!-- 15 --&gt;
&lt;p&gt;The old (&amp;ldquo;before&amp;rdquo;) behaviour here caused flake8 to raise &lt;code&gt;E302&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;some_var&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Leading sticky comment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;some_func&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_search_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queryset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;search_term&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; Return a tuple containing a queryset to implement the search
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; and a boolean indicating if the results may contain duplicates.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Apply keyword searches.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;construct_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; unchanged.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt; (&lt;a href="https://github.com/psf/black/pull/3302"target="_blank" rel="noopener"&gt;22.12.0+&lt;/a&gt;):&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;some_var&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Leading sticky comment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;some_func&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_search_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queryset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;search_term&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; Return a tuple containing a queryset to implement the search
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; and a boolean indicating if the results may contain duplicates.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Apply keyword searches.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;construct_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3&gt;Empty and whitespace-only files are now normalized&lt;span class="hx:absolute hx:-mt-20" id="empty-and-whitespace-only-files-are-now-normalized"&gt;&lt;/span&gt;
&lt;a href="#empty-and-whitespace-only-files-are-now-normalized" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Currently under the stable style, empty and whitespace-only files are not modified. In
some discussions (&lt;a href="https://github.com/psf/black/issues/2382"target="_blank" rel="noopener"&gt;issues&lt;/a&gt; and &lt;a href="https://github.com/psf/black/pull/2484"target="_blank" rel="noopener"&gt;pull requests&lt;/a&gt;), the consesus was to
reformat whitespace-only files to empty or single-character files, preserving the original
line endings (LR/CRLR) when possible.&lt;/p&gt;
&lt;p&gt;In the end, we settled on these rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Empty files are left as is&lt;/li&gt;
&lt;li&gt;Whitespace-only files (no newline) reformat into empty files&lt;/li&gt;
&lt;li&gt;Whitespace-only files (1 or more newlines) reformat into a single newline character&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://github.com/psf/black/pull/3348"target="_blank" rel="noopener"&gt;This was added to the preview style in 22.12.0&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Spyder cell separator comments (&lt;code&gt;#%%&lt;/code&gt;) are now normalized&lt;span class="hx:absolute hx:-mt-20" id="spyder-cell-separator-comments--are-now-normalized"&gt;&lt;/span&gt;
&lt;a href="#spyder-cell-separator-comments--are-now-normalized" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;!-- 10 --&gt;
&lt;p&gt;Black adds a space after the pound character in comments as per PEP 8. This caused trouble
as many editors have special comments for various functionality (eg. denoting regions in
the file) which could not contain any spaces.&lt;/p&gt;
&lt;p&gt;To play with other tools, Black leaves Spyder&amp;rsquo;s special &lt;code&gt;#%%&lt;/code&gt; comments (among other
special comments) alone.&lt;/p&gt;
&lt;p&gt;Sometime later, Spyder started to recognize &lt;code&gt;# %%&lt;/code&gt; alongside &lt;code&gt;#%%&lt;/code&gt;. So
&lt;a href="https://github.com/psf/black/pull/2919"target="_blank" rel="noopener"&gt;in Black 22.3.0, the preview style was changed&lt;/a&gt; to start adding spaces in &lt;code&gt;#%%&lt;/code&gt;
comments.&lt;/p&gt;
&lt;h2&gt;What is not being promoted?&lt;span class="hx:absolute hx:-mt-20" id="what-is-not-being-promoted"&gt;&lt;/span&gt;
&lt;a href="#what-is-not-being-promoted" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;!-- 3, 4, 14, 17 --&gt;
&lt;p&gt;&lt;em&gt;Ideally I would&amp;rsquo;ve provided examples for these changes too, but I need to get this post
out quickly and I&amp;rsquo;m getting tired. I&amp;rsquo;ve added links for further reading if you&amp;rsquo;re
curious.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/psf/black/pull/1132"target="_blank" rel="noopener"&gt;&lt;strong&gt;Experimental string processing&lt;/strong&gt;&lt;/a&gt; (aka ESP) is not being promoted to the 2023
stable preview. There&amp;rsquo;s a lot of reasons why, but
&lt;a href="https://github.com/psf/black/issues/3407#issuecomment-1345416311"target="_blank" rel="noopener"&gt;Jelle summarized it well in this comment&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[&amp;hellip;] At this point there&amp;rsquo;s still half a dozen stability bugs with experimental string
processing, so that alone is enough to block it from going into the 2023 stable style.
However, I think we should consider dropping (most of) the feature entirely, instead of
leaving it behind a flag forever, since there are so many stability bugs and often it
arguably makes for worse output. That should be discussed in &lt;a href="https://github.com/psf/black/issues/2188"target="_blank" rel="noopener"&gt;#2188&lt;/a&gt;, though.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And these three depend on ESP being promoted:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/psf/black/pull/3162"target="_blank" rel="noopener"&gt;Wrap implicitly concatenated strings inside a list, set, or tuple in parentheses (#3162)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/psf/black/pull/3307"target="_blank" rel="noopener"&gt;Wrap implicitly concatenated strings used as function args in parentheses (#3307)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/psf/black/pull/3227"target="_blank" rel="noopener"&gt;Fix a string merging/split issue caused by standalone comments (#3227)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additionally, two other changes are currently not slated for promotion and will remain in
the preview style until 2024, mostly since they were landed too late in the year to allow
for enough testing. These are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/psf/black/pull/3368"target="_blank" rel="noopener"&gt;For assignment statements, prefer splitting the right hand side if the left hand side fits on a single line (#3368)&lt;/a&gt;
~ &lt;em&gt;available in 22.12.0+&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/psf/black/pull/3440"target="_blank" rel="noopener"&gt;Improve long values in dict literals (#3440)&lt;/a&gt; ~ &lt;em&gt;available on main and in 23.1a1
only&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&amp;ldquo;I want a &lt;em&gt;new&lt;/em&gt; change to the stable style&amp;rdquo;&lt;span class="hx:absolute hx:-mt-20" id="i-want-a-new-change-to-the-stable-style"&gt;&lt;/span&gt;
&lt;a href="#i-want-a-new-change-to-the-stable-style" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;If you want a change that&amp;rsquo;s not covered by something in the preview style already (and
can&amp;rsquo;t be achieved by simply tweaking a pre-existing style change), check the issue tracker
for a issue that covers what you want. If you don&amp;rsquo;t find one,
&lt;a href="https://github.com/psf/black/issues/new?assignees=&amp;amp;labels=T%3A&amp;#43;design&amp;amp;template=style_issue.md&amp;amp;title="target="_blank" rel="noopener"&gt;file an issue here&lt;/a&gt; or &lt;a href="https://github.com/psf/black/issues/3407"target="_blank" rel="noopener"&gt;drop a comment here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Although, if you&amp;rsquo;re hoping to squeeze in a new major style change into the 2023 &lt;strong&gt;stable&lt;/strong&gt;
style, that probably won&amp;rsquo;t be possible being so late in the year. It can always be
promoted to stable for 2024.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;There&amp;rsquo;s some interesting history to why we added this flag, but I don&amp;rsquo;t have time to
get into it right now.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>What's new in Black 22.10.0</title><link>https://sichard.ca/blog/2022/10/black-22.10.0/</link><pubDate>Fri, 14 Oct 2022 00:00:00 -0400</pubDate><guid>https://sichard.ca/blog/2022/10/black-22.10.0/</guid><description>
&lt;p&gt;On October 6th, 2022, &lt;a href="https://github.com/psf/black/releases/tag/22.10.0"target="_blank" rel="noopener"&gt;we released Black 22.10.0&lt;/a&gt;. It&amp;rsquo;s a smaller release
by the size of the changelog, but hey that means I don&amp;rsquo;t have to write as much!&lt;/p&gt;
&lt;p&gt;This is meant to be an accompanying blog post for 22.10.0. I won&amp;rsquo;t discuss every change,
just the ones worth highlighting. Instead, I&amp;rsquo;ll go into more detail, providing context and
explanations (and probably some behind of scenes stuff too &amp;ndash; this isn&amp;rsquo;t official anyway).
I know this is a bit late, but I hope this is still insightful.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;d like the full changelog for 22.10.0, please refer to the link above.&lt;/p&gt;
&lt;h2&gt;Packaging&lt;span class="hx:absolute hx:-mt-20" id="packaging"&gt;&lt;/span&gt;
&lt;a href="#packaging" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3&gt;Goodbye Python 3.6 (and hello Hatchling!)&lt;span class="hx:absolute hx:-mt-20" id="goodbye-python-36-and-hello-hatchling"&gt;&lt;/span&gt;
&lt;a href="#goodbye-python-36-and-hello-hatchling" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;It&amp;rsquo;s official: &lt;strong&gt;you can no longer run Black on Python 3.6 starting with 22.10.0&lt;/strong&gt;.&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;This is actually the first time Black has ever dropped runtime support since 3.6+ was
required from the start.&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;We dropped Python 3.6 because we wanted to switch build backends from Setuptools to
&lt;a href="https://hatch.pypa.io/"target="_blank" rel="noopener"&gt;Hatchling&lt;/a&gt; which only supports 3.7+. Setuptools has served us well as a simple and
reliable backend, but we wanted to modernize. While Setuptools does finally now support
most packaging standards (PEP 517, PEP 621, PEP 660 and more) our needs are a bit unique.&lt;/p&gt;
&lt;p&gt;We use &lt;a href="https://mypyc.readthedocs.io/"target="_blank" rel="noopener"&gt;mypyc&lt;/a&gt; to compile Black for a significant performance boost. Before Hatchling we
hand-rolled our own integration using mypyc&amp;rsquo;s basic Setuptools build API. It worked well
enough, but it meant we would be stuck with &lt;code&gt;setup.py&lt;/code&gt; forever until a Setuptools plugin
for mypyc is available. (&lt;em&gt;one does not exist as of writing&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;All of the reasons behind the switch &lt;a href="https://github.com/psf/black/pull/3233"target="_blank" rel="noopener"&gt;can be found in the PR&lt;/a&gt;, but in essence,
Hatchling allowed us to move to static metadata in &lt;code&gt;pyproject.toml&lt;/code&gt; while providing us
with a nicer mypyc integration thanks to its &lt;a href="https://github.com/ofek/hatch-mypyc"target="_blank" rel="noopener"&gt;hatch-mypyc&lt;/a&gt; plugin.&lt;/p&gt;
&lt;p&gt;Switching to Hatchling hasn&amp;rsquo;t been as simple as I would&amp;rsquo;ve wished: we had to track down
&lt;a href="https://github.com/psf/black/pull/3272"target="_blank" rel="noopener"&gt;this confusing linker error&lt;/a&gt; that only appears when using build isolation (among
other situations) and now our &lt;a href="https://github.com/psf/black/issues/3312"target="_blank" rel="noopener"&gt;macOS builds are producing mislabelled wheels&lt;/a&gt;,
ugh. It happens.&lt;/p&gt;
&lt;h3&gt;Compiled wheels for 3.11&lt;span class="hx:absolute hx:-mt-20" id="compiled-wheels-for-311"&gt;&lt;/span&gt;
&lt;a href="#compiled-wheels-for-311" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;We couldn&amp;rsquo;t build mypyc wheels for CPython 3.11 since we had been using an older manylinux
docker image to avoid the linker error mentioned earlier. It was old enough that 3.11
wasn&amp;rsquo;t pre-installed!&lt;/p&gt;
&lt;p&gt;Once the linker error was addressed by &lt;a href="https://github.com/zsol"target="_blank" rel="noopener"&gt;@zsol&lt;/a&gt; (thank you, I had no chance of figuring it
out), it was pretty straightforward to configure cibuildwheel to build CPython 3.11
wheels.
&lt;a href="https://github.com/psf/black/pull/3276"target="_blank" rel="noopener"&gt;Just a CIBW upgrade, a type error fix, and a workaround so that aiohttp doesn&amp;rsquo;t fail to install on 3.11.&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Code style&lt;span class="hx:absolute hx:-mt-20" id="code-style"&gt;&lt;/span&gt;
&lt;a href="#code-style" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3&gt;Failing to put &lt;code&gt;fmt: on&lt;/code&gt; at the same indent level as &lt;code&gt;fmt: off&lt;/code&gt; no longer crashes&lt;span class="hx:absolute hx:-mt-20" id="failing-to-put-fmt-on-at-the-same-indent-level-as-fmt-off-no-longer-crashes"&gt;&lt;/span&gt;
&lt;a href="#failing-to-put-fmt-on-at-the-same-indent-level-as-fmt-off-no-longer-crashes" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Previously Black produced invalid code when &lt;code&gt;# fmt: on&lt;/code&gt; is placed on a different indent
level than the &lt;code&gt;# fmt: off&lt;/code&gt; it’s paired with. Black failed similarly when &lt;code&gt;# fmt: on&lt;/code&gt; just
didn&amp;rsquo;t exist.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;x&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# fmt: off&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;y&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; black issue-569.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;error: cannot format issue-569.py: INTERNAL ERROR: Black produced code that is not equivalent to the source. Please report a bug on https://github.com/psf/black/issues. This diff might be helpful: /tmp/blk_wv8p1p5k.log
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Oh no! 💥 💔 💥
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;1 file failed to reformat.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;If you disable the safety checks with &lt;code&gt;--fast&lt;/code&gt;, you&amp;rsquo;ll quickly realize that Black badly
messed up the indentation.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;x&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# fmt: off&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;y&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This annoying bug was often hit. If you look at the &lt;a href="https://github.com/psf/black/issues/569"target="_blank" rel="noopener"&gt;original issue&amp;rsquo;s timeline&lt;/a&gt;,
you&amp;rsquo;ll notice that this bug has been reported many, &lt;em&gt;many&lt;/em&gt; times over. Yet, this bug was
left unfixed for almost three years until &lt;a href="https://github.com/yilei"target="_blank" rel="noopener"&gt;@yilei&lt;/a&gt; opened &lt;a href="https://github.com/psf/black/pull/3281"target="_blank" rel="noopener"&gt;GH-3281&lt;/a&gt; to finally fix it.&lt;/p&gt;
&lt;p&gt;Black still requires &lt;code&gt;# fmt: off&lt;/code&gt; and &lt;code&gt;# fmt: on&lt;/code&gt; to be used at the same indent level, but
now, the code at or above the initial &lt;code&gt;# fmt: off&lt;/code&gt;&amp;rsquo;s block level will be left untouched,
when &lt;code&gt;# fmt: on&lt;/code&gt; is used on a different level or there is no &lt;code&gt;# fmt: on&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In other words, you can now use a single &lt;code&gt;# fmt: off&lt;/code&gt; to turn off formatting by block
level.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ignore_this_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# fmt: off&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;hello, world&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;format_this_function&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;except_this_block_level&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# fmt: off&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;hello, world&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;except_this_block_level&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ black file.py --diff --color
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;--- file.py 2022-10-15 18:30:53.887339 +0000
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+++ file.py 2022-10-15 18:30:54.780816 +0000
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -7,6 +7,6 @@
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; def format_this_function():
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; def except_this_block_level():
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; # fmt: off
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return &amp;#39;hello, world&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- return except_this_block_level ()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ return except_this_block_level()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Anyway, I&amp;rsquo;m so happy this major bug has finally been fixed. Thank you so much @yilei!&lt;/p&gt;
&lt;h3&gt;Preview style: fixed a crash related to string keys in dictionaries&lt;span class="hx:absolute hx:-mt-20" id="preview-style-fixed-a-crash-related-to-string-keys-in-dictionaries"&gt;&lt;/span&gt;
&lt;a href="#preview-style-fixed-a-crash-related-to-string-keys-in-dictionaries" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;a href="https://github.com/psf/black/pull/3162"target="_blank" rel="noopener"&gt;Parentheses are added around implicitly concatenated strings in various situations under the preview style&lt;/a&gt;
since 22.8.0. Unfortunately, this introduced a bug where long string keys would be wrapped
in extra parentheses along with the value.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a_very_long_str_a_very_long_str_a_very_long_str_a_very_long_str_&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;a_very_long_str_a_very_long_str_a_very_long_str_&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;key&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;value&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; v = {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- (&amp;#39;a_very_long_str_a_very_long_str_a_very_long_str_a_very_long_str_&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- &amp;#39;a_very_long_str_a_very_long_str_a_very_long_str_&amp;#39;): {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- &amp;#39;key&amp;#39;:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- &amp;#39;value&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ (
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ &amp;#34;a_very_long_str_a_very_long_str_a_very_long_str_a_very_long_str_a_very_long_str_a_very_long_str_a_very_long_str_&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ &amp;#34;key&amp;#34;: &amp;#34;value&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ ),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Now Black no longer adds these invalid parentheses, although it just merges the strings
together violating the line length which doesn&amp;rsquo;t seem right&amp;hellip;&lt;/p&gt;
&lt;h2&gt;GitHub Action improvements&lt;span class="hx:absolute hx:-mt-20" id="github-action-improvements"&gt;&lt;/span&gt;
&lt;a href="#github-action-improvements" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;The GitHub Action can now format Jupyter Notebooks if you set &lt;code&gt;jupyter: true&lt;/code&gt;:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;psf/black@stable&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;./src&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jupyter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;22.10.0&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Under the hood, it will install Black with the &lt;code&gt;jupyter&lt;/code&gt; extra enabling Black&amp;rsquo;s optional
support for &lt;code&gt;.ipynb&lt;/code&gt; files.&lt;/p&gt;
&lt;p&gt;Additionally, the &lt;code&gt;version&lt;/code&gt; key now supports &lt;a href="https://peps.python.org/pep-0440/"target="_blank" rel="noopener"&gt;PEP 440&lt;/a&gt; version specifiers. This is a great
QOL improvement because if you want to match versions covered by Black’s
&lt;a href="https://black.readthedocs.io/en/stable/the_black_code_style/index.html#labels-stability-policy"target="_blank" rel="noopener"&gt;stability policy&lt;/a&gt;, you can use the compatible release operator (&lt;code&gt;~=&lt;/code&gt;) to do that now:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;psf/black@stable&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;./src&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;~= 22.0&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;By restricting to 2022 releases, the latest version possible will be used without sudden
changes (breaking your CI) since releases within the same year are guaranteed to not
change the stable style.&lt;/p&gt;
&lt;p&gt;This was a backwards-compatible change, you can still specify a single version if you&amp;rsquo;d
like.&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;-x&lt;/code&gt; / &lt;code&gt;--skip-source-first-line&lt;/code&gt;&lt;span class="hx:absolute hx:-mt-20" id="-x----skip-source-first-line"&gt;&lt;/span&gt;
&lt;a href="#-x----skip-source-first-line" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://docs.python.org/dev/using/cmdline.html#cmdoption-x"target="_blank" rel="noopener"&gt;Python has a flag (&lt;code&gt;-x&lt;/code&gt;) to skip the first line of files given&lt;/a&gt;. I didn&amp;rsquo;t know this
existed, but it turns out it&amp;rsquo;s commonly used with Python WASM pages. They shove the HTML
code in a single line at the start as a non-standard &amp;ldquo;shebang&amp;rdquo; (since browsers generally
require HTML files to start with &lt;code&gt;&amp;lt;&lt;/code&gt;) so it can run in the browser &lt;em&gt;and&lt;/em&gt; locally.&lt;/p&gt;
&lt;p&gt;For example, &lt;a href="https://pygame-web.github.io/showroom/pygame-scripts/org.pygame.touchpong.html"target="_blank" rel="noopener"&gt;this browser pong game uses pygame&lt;/a&gt; via &lt;a href="https://pypi.org/project/pygbag/"target="_blank" rel="noopener"&gt;pygbag&lt;/a&gt; (a WASM
version of pygame). The start of the HTML file looks like this:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://pygame-web.github.io/archives/0.3/pythons.js&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;module&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;data-src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;gui&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;async&lt;/span&gt; &lt;span class="na"&gt;defer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;#&amp;lt;!--&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;import sys
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;import os
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;import asyncio&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Since all the HTML code is at the top in a single line, you can run it as any other Python
script by simply passing &lt;code&gt;-x&lt;/code&gt;.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;wget https://pygame-web.github.io/showroom/pygame-scripts/org.pygame.touchpong.html
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;#&lt;/span&gt; assuming pygame is installed
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;python -x org.pygame.touchpong.html
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I was hesitant to add a similar option to Black, but it was a convincing use-case and
&lt;a href="https://github.com/psf/black/issues/3214"target="_blank" rel="noopener"&gt;the issue got a surprising amount of upvotes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This option is also available for Blackd.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&amp;hellip; and that&amp;rsquo;s all. Thanks for reading! ❀&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;We weren&amp;rsquo;t planning to remove 3.6 support in 22.10.0. We believed that Python 3.6 was
too popular to stop supporting. The earliest agreed-upon removal date was 2023. But
after even more internal discussion,
&lt;a href="https://github.com/psf/black/issues/3169#issuecomment-1221624251"target="_blank" rel="noopener"&gt;we eventually decided to drop 3.6 earlier&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Łukasz couldn&amp;rsquo;t live without his f-strings :)&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Compiling Black with mypyc, Pt. 3 - Deployment</title><link>https://sichard.ca/blog/2022/05/compiling-black-with-mypyc-part-3/</link><pubDate>Tue, 31 May 2022 16:27:00 -0400</pubDate><guid>https://sichard.ca/blog/2022/05/compiling-black-with-mypyc-part-3/</guid><description>
&lt;p&gt;This is part of the &amp;ldquo;&lt;em&gt;Compiling Black with mypyc&lt;/em&gt;&amp;rdquo; series.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="../compiling-black-with-mypyc-part-1/"&gt;Pt. 1 - Initial Steps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="../compiling-black-with-mypyc-part-2/"&gt;Pt. 2 - Optimization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="."&gt;Pt. 3 - Deployment&lt;/a&gt; (you&amp;rsquo;re reading this one)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Building compiled wheels with GitHub Actions&lt;span class="hx:absolute hx:-mt-20" id="building-compiled-wheels-with-github-actions"&gt;&lt;/span&gt;
&lt;a href="#building-compiled-wheels-with-github-actions" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;With the mypyc branch functional and pretty fast, it was time to automate building the
wheels. Not only does this make the release process easier, but &lt;strong&gt;it also means I can
easily build wheels for platforms I don&amp;rsquo;t have access to&lt;/strong&gt;, like MacOS.&lt;/p&gt;
&lt;p&gt;To make the CI configuration process easier, I finally took a look at &lt;a href="https://cibuildwheel.readthedocs.io/en/stable/"target="_blank" rel="noopener"&gt;cibuildwheel&lt;/a&gt;. I
won&amp;rsquo;t go into my exact process since it was basically trial and error + my many dumb
mistakes 😅 &lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; but here are some noteworthy takeaways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If you&amp;rsquo;re using &lt;code&gt;setuptools-scm&lt;/code&gt;, you might have to do a full clone on CI so the tag
history is still available by build time&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Read cibuildwheel&amp;rsquo;s documentation before writing any configuration!&lt;/strong&gt; Seriously, if I
did I wouldn&amp;rsquo;t have to make &lt;a href="https://github.com/ichard26/black-mypyc-wheels/commit/643de450e252e74040ba14fd066ea8bc23c0b0d7"target="_blank" rel="noopener"&gt;this commit&lt;/a&gt; adding &lt;code&gt;{project}&lt;/code&gt; to
the test command&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;CIBW_BUILD_VERBOSITY&lt;/code&gt; to at least &lt;code&gt;1&lt;/code&gt; because it will make debugging build errors
(and trust me you will get some!) so much nicer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If wheel sizes are an issue then passing &lt;code&gt;debug_level=0&lt;/code&gt; to &lt;code&gt;mypyc.build.mypycify&lt;/code&gt;
should help by stripping all debug information. Not great if you hit a bunch of
segfaults or similar though, so it&amp;rsquo;s a tradeoff&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So yeah, 20-ish commits later I had a basic but functional setup which could compile
wheels for x86-64 Windows, MacOS, and Linux from CPython 3.6 to CPython 3.10. Universial2
and ARM variants are also supported for the shiny M1 platform. The workflow is a bit slow,
but that&amp;rsquo;s expected and not a big deal.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re curious, you can find the workflow here: &lt;a href="https://github.com/ichard26/black-mypyc-wheels"target="_blank" rel="noopener"&gt;ichard26/black-mypyc-wheels&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I don&amp;rsquo;t recommend using my workflow to build your own wheels since it&amp;rsquo;s a bit hacky and
in need of a cleanup. I&amp;rsquo;d instead recommend taking a look at the
&lt;a href="https://cibuildwheel.readthedocs.io/en/stable/working-examples/"target="_blank" rel="noopener"&gt;examples in cibuildwheel&amp;rsquo;s docs&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I was going to mention the community field testing campaign I carried out that had its own
package index, but honestly it was for the most part a failure as it reached no one. In
hindsight, I did not promote it enough so yeah this one is on me :)&lt;/p&gt;
&lt;h2&gt;Stable release prep and shipping the wheels&lt;span class="hx:absolute hx:-mt-20" id="stable-release-prep-and-shipping-the-wheels"&gt;&lt;/span&gt;
&lt;a href="#stable-release-prep-and-shipping-the-wheels" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Black for the longest time ever wasn&amp;rsquo;t stable (see &lt;a href="https://github.com/psf/black/issues/517"target="_blank" rel="noopener"&gt;GH-517&lt;/a&gt;). The team initially aimed to
stabilize Black in late 2018, but that didn&amp;rsquo;t happen for reasons. I won&amp;rsquo;t go into it too
much since it&amp;rsquo;d be a whole other story, but suffice to say when we made our newest
&amp;ldquo;commitment&amp;rdquo; (at that point we explicitly worded our intentions to &lt;em&gt;not&lt;/em&gt; be promises) &lt;strong&gt;we
were very motivated to get it done and hopefully right.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We drafted a &lt;a href="https://black.readthedocs.io/en/latest/the_black_code_style/index.html#stability-policy"target="_blank" rel="noopener"&gt;stability policy&lt;/a&gt;,
&lt;a href="https://github.com/psf/black/pull/2740"target="_blank" rel="noopener"&gt;dropped Python 2 support&lt;/a&gt;, &lt;a href="https://github.com/psf/black/issues/2751"target="_blank" rel="noopener"&gt;introduced the &lt;code&gt;--preview&lt;/code&gt; flag&lt;/a&gt;,
and &lt;a href="https://black.readthedocs.io/en/stable/change_log.html"target="_blank" rel="noopener"&gt;so much more&lt;/a&gt; &lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;In this time I rewrote diff-shades into &lt;a href="https://github.com/ichard26/diff-shades"target="_blank" rel="noopener"&gt;what it is today&lt;/a&gt;, a reliable enough
tool used to &lt;a href="https://github.com/psf/black/pull/2814#issuecomment-1023219426"target="_blank" rel="noopener"&gt;provide direct&lt;/a&gt;
&lt;a href="https://github.com/psf/black/pull/2726#issuecomment-1019067134"target="_blank" rel="noopener"&gt;feedback on PRs&lt;/a&gt;. I tried to integrate it into the wheel build
workflow as part of the test step, but that turned out be very painful and I backed out of
it since I had a stable release to manage and publish!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The TL;DR of it is as follows:&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Building Black with mypyc usually requires disabling pip&amp;rsquo;s build isolation to work
properly as mypyc is not a standard build dependency of Black. This messes up the
installation of diff-shades which is packaged using flit. So I hacked up a script to edit
the &lt;code&gt;[build-system].requires&lt;/code&gt; field in &lt;code&gt;pyproject.toml&lt;/code&gt; to include mypyc pre-build, but
the isolation somehow broke the linker search path or something.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;clang -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -g0 -fPIC -I/tmp/pip-build-env-9bfrmy6j/overlay/lib/python3.7/site-packages/mypyc/lib-rt -Ibuild -I/opt/python/cp37-cp37m/include/python3.7m -c build/__native_f2d4935fd652bc9ef29d.c -o build/temp.linux-x86_64-3.7/build/__native_f2d4935fd652bc9ef29d.o -O3 -Werror -Wno-unused-function -Wno-unused-label -Wno-unreachable-code -Wno-unused-variable -Wno-unused-command-line-argument -Wno-unknown-warning-option
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; clang -shared -g0 build/temp.linux-x86_64-3.7/build/__native_f2d4935fd652bc9ef29d.o -o build/lib.linux-x86_64-3.7/f2d4935fd652bc9ef29d__mypyc.cpython-37m-x86_64-linux-gnu.so
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /usr/bin/ld: cannot find crtbeginS.o: No such file or directory
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /usr/bin/ld: cannot find -lgcc
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /usr/bin/ld: cannot find -lgcc_s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; clang: error: linker command failed with exit code 1 (use -v to see invocation)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; error: command &amp;#39;/usr/bin/clang&amp;#39; failed with exit code 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I honestly still have no idea what&amp;rsquo;s wrong.&lt;/p&gt;
&lt;p&gt;Anyway since I was like an hour or more into build errors, I just decided to stop and fall
back to the basic testing that was already working.&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt; I triggered the last workflow run
of the day, downloaded the artifacts, tested one of them locally to make sure nothing was
on fire, and pushed &amp;rsquo;em to PyPI and that&amp;rsquo;s how release 22.1.0 was born 🎉.&lt;/p&gt;
&lt;p&gt;The core team chat was quite lively for the next hour, to say the least; after all, we
just finished a major milestone! Also yeah, we &lt;em&gt;may&lt;/em&gt; have done a lot of publicizing for
this release which is why it was everywhere for a while :wink:&lt;/p&gt;
&lt;h3&gt;Post release calm&lt;span class="hx:absolute hx:-mt-20" id="post-release-calm"&gt;&lt;/span&gt;
&lt;a href="#post-release-calm" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;To be honest I haven&amp;rsquo;t really seen anyone comment on the fact Black is now compiled with
mypyc yet short of this one &lt;a href="https://simonwillison.net/2022/Jan/30/mypyc/"target="_blank" rel="noopener"&gt;mini blog post&lt;/a&gt;&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt; &amp;hellip;
which is both disappointing since I spent so much time on this effort, but also calming
since usually buzz is sparked by bugs and whatnot.&lt;/p&gt;
&lt;p&gt;Having mentioned bugs and crashes, so far we&amp;rsquo;ve received two reports,
&lt;a href="https://github.com/psf/black/issues/2846"target="_blank" rel="noopener"&gt;one bug probably from mypyc and another due to an unintentional restriction of Black&amp;rsquo;s unofficial APIs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Version 22.1.0 still unintentionally broke quite a few integrations, though they weren&amp;rsquo;t
mypyc related. Turns out lots of people depend on &lt;code&gt;black.files.find_project_root&lt;/code&gt;
returning a &lt;code&gt;pathlib.Path&lt;/code&gt; and nothing else! I&amp;rsquo;ve seen at least five issues / PRs on
GitHub fixing crashes related to our change making it return a tuple instead.&lt;/p&gt;
&lt;p&gt;For this reason we, the core team, want to &lt;a href="https://github.com/psf/black/issues/779"target="_blank" rel="noopener"&gt;define a stable API (GH-779)&lt;/a&gt; soon. I
can&amp;rsquo;t promise anything as Black&amp;rsquo;s development is volunteer-based, but if you were curious
to what&amp;rsquo;s next for psf/black, there&amp;rsquo;s this.&lt;/p&gt;
&lt;h2&gt;Results &amp;amp; final thoughts&lt;span class="hx:absolute hx:-mt-20" id="results--final-thoughts"&gt;&lt;/span&gt;
&lt;a href="#results--final-thoughts" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Black is now overall 2 times faster&lt;/strong&gt;&lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt;. And as a bonus, startup time is down too (at
most by 15%). You can &lt;a href="https://gist.github.com/ichard26/b996ccf410422b44fcd80fb158e05b0d"target="_blank" rel="noopener"&gt;read the whole report here&lt;/a&gt;. Please note these numbers
were gathered quite a long time ago and &lt;strong&gt;they are probably a bit outdated&lt;/strong&gt;, especially
with the somewhat recent blib2to3 changes made to support 3.10 syntax. If you&amp;rsquo;d like to
see what all of this work looked in PR form, &lt;a href="https://github.com/psf/black/pull/2431"target="_blank" rel="noopener"&gt;here ya go&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;From my experience, I believe mypyc will make for an interesting, but viable solution to
speeding up Python code going forward. It&amp;rsquo;s far from perfect and still has a bunch of
bugs, but today it is already making an impact. I don&amp;rsquo;t recommend using mypyc unless you
are willing to feel a bit like a beta tester &lt;em&gt;and&lt;/em&gt; have an exhaustive test suite, but
&lt;strong&gt;hey I managed to do all of this without knowing much about programming in C&lt;/strong&gt;&lt;sup id="fnref:6"&gt;&lt;a href="#fn:6" class="footnote-ref" role="doc-noteref"&gt;6&lt;/a&gt;&lt;/sup&gt;,
which says a lot.&lt;/p&gt;
&lt;p&gt;Going forward these open issues remain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;pre-commit is still slow&lt;/strong&gt;: since pre-commit downloads hooks from source, the compiled
wheels won&amp;rsquo;t be used. This is a shame since Black&amp;rsquo;s speed is noticeable during git
commits. I suspect we will have to introduce a shim like mypy&amp;rsquo;s
&lt;a href="https://github.com/pre-commit/mirrors-mypy"target="_blank" rel="noopener"&gt;pre-commit/mirrors-mypy&lt;/a&gt;, but that will be a painful transition.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Wheel building is slow&lt;/strong&gt;: this is because how mypyc&amp;rsquo;s setuptools integration works
with PEP 517 builds. The source code ends up being parsed, type checked, and
transcompiled twice during a build. &lt;a href="https://github.com/pypa/hatch"target="_blank" rel="noopener"&gt;pypa/hatch&lt;/a&gt; might be a good way to fix this
assuming its mypyc plugin doesn&amp;rsquo;t suffer the same flaw.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;We&amp;rsquo;re stuck on mypyc 0.920&lt;/strong&gt;: a change to newer versions broke our usage of
dataclasses and it still hasn&amp;rsquo;t been fixed &amp;hellip;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m optimistic that it&amp;rsquo;s only a matter of time before all of these issues are resolved.&lt;/p&gt;
&lt;p&gt;I hope to see the mypyc project gain more contributors and succeed. Along with Cython,
PyPy, and the faster-cpython project, &lt;strong&gt;perhaps Python will finally have a solid speed
story&lt;/strong&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;And yes, I did indeed learn a ton embarking on this project. Made mistakes along the
way, but they turned out alright ❀&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Congrats on reaching the end of this multi-part blog series!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;By the way, &lt;a href="https://glyph.twistedmatrix.com/2022/04/you-should-compile-your-python-and-heres-why.html"target="_blank" rel="noopener"&gt;Glyph has also written an excellent piece on mypyc&lt;/a&gt; where he tries to
convince you to start using mypyc. You should totally read it.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you would like to chat with me, &lt;a href="https://sichard.ca/about/#contact-information"target="_blank" rel="noopener"&gt;my contact details are here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Acknowledgements&lt;span class="hx:absolute hx:-mt-20" id="acknowledgements"&gt;&lt;/span&gt;
&lt;a href="#acknowledgements" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I&amp;rsquo;d like to thank &lt;a href="https://github.com/msullivan"target="_blank" rel="noopener"&gt;@msullivan&lt;/a&gt; for his original work on integrating mypyc into Black,
spawning this summer project and eventually this blog series :)&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d like to also thank &lt;a href="https://github.com/dawnofmidnight"target="_blank" rel="noopener"&gt;@dawnofmidnight&lt;/a&gt;, &lt;a href="https://github.com/kosayoda"target="_blank" rel="noopener"&gt;@kosayoda&lt;/a&gt;, &lt;a href="https://github.com/JelleZijlstra"target="_blank" rel="noopener"&gt;Jelle Zijlstra&lt;/a&gt;,
and &lt;a href="https://github.com/ambv"target="_blank" rel="noopener"&gt;Łukasz Langa&lt;/a&gt; for their extensive feedback and help while writing this series.
Any errors are my own, not theirs.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;If you are in need of some quality entertainment, look at this commit history:
&lt;a href="https://github.com/ichard26/black-mypyc-wheels/commits/main"target="_blank" rel="noopener"&gt;https://github.com/ichard26/black-mypyc-wheels/commits/main&lt;/a&gt;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;I would link to the 22.1.0 heading, but currently the HTML IDs are uhhh &amp;hellip; terrible
and don&amp;rsquo;t persist. I&amp;rsquo;ve tried fixing this with a custom sphinx extension but it
failed. I have other ideas left to try, but yeah :(&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;I was frustrated and had a headache thanks to the stress 🙃&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;In the time it has taken me to edit this series, Łukasz Langa has done a &lt;a href="https://pretalx.com/pycon-lt-2022/talk/BG7LZF/"target="_blank" rel="noopener"&gt;talk&lt;/a&gt; about
mypyc and Black&amp;rsquo;s usage of it.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;Originally when I first landed the relevant PR it was an overall 2x improvement, but
once Jelle added a stability hotfix the &lt;em&gt;effective&lt;/em&gt; speedup for files that are changed
is 50%. If you&amp;rsquo;re formatting a bunch of already well formatted files, the speedup is
still 2x&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:6"&gt;
&lt;p&gt;Learning C (and Rust) is now one of my future goals thanks to this project&amp;#160;&lt;a href="#fnref:6" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Compiling Black with mypyc, Pt. 2 - Optimization</title><link>https://sichard.ca/blog/2022/05/compiling-black-with-mypyc-part-2/</link><pubDate>Tue, 31 May 2022 16:26:00 -0400</pubDate><guid>https://sichard.ca/blog/2022/05/compiling-black-with-mypyc-part-2/</guid><description>
&lt;p&gt;This is part of the &amp;ldquo;&lt;em&gt;Compiling Black with mypyc&lt;/em&gt;&amp;rdquo; series.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="../compiling-black-with-mypyc-part-1/"&gt;Pt. 1 - Initial Steps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="."&gt;Pt. 2 - Optimization&lt;/a&gt; (you&amp;rsquo;re reading this one)&lt;/li&gt;
&lt;li&gt;&lt;a href="../compiling-black-with-mypyc-part-3/"&gt;Pt. 3 - Deployment&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Optimizing for mypyc&lt;span class="hx:absolute hx:-mt-20" id="optimizing-for-mypyc"&gt;&lt;/span&gt;
&lt;a href="#optimizing-for-mypyc" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Having compiled Black successfully without it blowing up, it was time to try to optimize
Black for mypyc. While mypyc is designed to handle all sorts of statically typed code,
&lt;strong&gt;changing up the code even a little bit can allow mypyc to perform additional
optimizations&lt;/strong&gt;, ultimately helping performance a bunch.&lt;/p&gt;
&lt;p&gt;And while I did go into this hoping I could spot some potential architectural or data
structure optimizations, I wasn&amp;rsquo;t able to so the optimizations shared will be mypyc
specific.&lt;/p&gt;
&lt;h3&gt;Getting started&lt;span class="hx:absolute hx:-mt-20" id="getting-started"&gt;&lt;/span&gt;
&lt;a href="#getting-started" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;First off, profiling! It&amp;rsquo;s hard to optimize code well if you don&amp;rsquo;t know where time is
being spent.&lt;/p&gt;
&lt;p&gt;To start off, I profiled Black over some of its own source code with cProfile to get a
birds eye view:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; python -m cProfile -o profile.pstats -m black src/black/__init__.py --check
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;All done! ✨ 🍰 ✨
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;1 file would be left unchanged.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; gprof2dot profile.pstats &lt;span class="p"&gt;|&lt;/span&gt; dot -Tsvg -o profile.svg
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;With the help of &lt;a href="https://github.com/jrfonseca/gprof2dot"target="_blank" rel="noopener"&gt;gprof2dot&lt;/a&gt; (well, actually the &lt;a href="https://pypi.org/project/yelp-gprof2dot/"target="_blank" rel="noopener"&gt;yelp-gprof2dot&lt;/a&gt; fork), I converted the
profiling data into a nice SVG graph which I could then open in my web browser.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tip: please open this massive SVG in a new tab because viewing it here will probably be
painful :)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="https://sichard.ca/media/cProfileGraph-black-1.svg" alt="gprof2dot graph showing where time was spent in a call tree" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;I tried using &lt;a href="https://pypi.org/project/scalene/"target="_blank" rel="noopener"&gt;Scalene&lt;/a&gt; as I&amp;rsquo;ve heard good things about it, but it didn&amp;rsquo;t work sadly. It
wasn&amp;rsquo;t that bad as I still had &lt;a href="https://github.com/benfred/py-spy"target="_blank" rel="noopener"&gt;py-spy&lt;/a&gt;, &lt;a href="https://pypi.org/project/line-profiler/"target="_blank" rel="noopener"&gt;line_profiler&lt;/a&gt;, and good ol&amp;rsquo; cProfile. py-spy in
particular was invaluable since it can profile (well err sample) C extensions which
cProfile cannot. line_profiler was used exclusively for micro-optimizations :p&lt;/p&gt;
&lt;p&gt;Anyway, I repeated this process quite a few times, making sure to try different files to
get a general feel where time is going regardless of the input. Here are the main
takeaways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Initial parsing with blib2to3 takes up 30-50% of formatting runtime!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The AST equivalent safety check is cheap at 5-10% (obviously only when changes were
made, otherwise it&amp;rsquo;s zero 🙂)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The actual formatting logic&amp;rsquo;s runtime is mostly spent in the CST visitor usually taking
up 75%. The rest went to the transformers which handle line breaks, string literals, and
some special cases.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I was quite surprised how much time blib2to3 related functions were eating up. Just look
how concentrated the hotspots really are!&lt;/p&gt;
&lt;p&gt;&lt;img src="https://sichard.ca/media/cProfileGraph-black-blib2to3-1.svg" alt="gprof2dot graph showing where time was spent parsing" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;These hotspots make this part of the codebase way easier to optimize, so I optimized
blib2to3 first.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s been so long since I first looked into this, so I don&amp;rsquo;t remember what optimizations I
tried initially, but I do remember them having no effect :(&lt;/p&gt;
&lt;p&gt;I tried other things and they actually helped 🎉 &amp;hellip; probably since I took into account how
mypyc works. Ultimately, many different optimizations were done over three rounds.&lt;/p&gt;
&lt;h4&gt;Tightening up existing type annotations&lt;span class="hx:absolute hx:-mt-20" id="tightening-up-existing-type-annotations"&gt;&lt;/span&gt;
&lt;a href="#tightening-up-existing-type-annotations" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;The stricter the type annotations are in your codebase, the more invariants mypyc will be
able to infer. It&amp;rsquo;ll then use this information to write type-specific code that is faster.
This code won&amp;rsquo;t work if it gets an object of a different type, but that&amp;rsquo;s why we use and
enforce type annotations, so mypyc can safely assume it&amp;rsquo;s going to be the right type. &lt;strong&gt;In
essence, the stricter the type annotations, the faster code mypyc can generate.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This meant reading through the code&amp;rsquo;s control flow, checking whether certain states are
impossible. Blib2to3 is a legacy codebase, so type annotations were added to unblock other
work. The goal was to make blib2to3 type check, and not write perfectly typed code. So
naturally, there were a few permissive (parameter) type annotations I could make stricter:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;diff --git a/src/blib2to3/pgen2/parse.py b/src/blib2to3/pgen2/parse.py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;index 47c8f02..6b03188 100644
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;--- a/src/blib2to3/pgen2/parse.py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+++ b/src/blib2to3/pgen2/parse.py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -138,7 +138,7 @@ class Parser(object):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; self.rootnode: Optional[NL] = None
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; self.used_names: Set[str] = set()
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- def addtoken(self, type: int, value: Optional[Text], context: Context) -&amp;gt; bool:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ def addtoken(self, type: int, value: Text, context: Context) -&amp;gt; bool:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;&amp;#34;&amp;#34;Add a token; return True iff this is the end of the program.&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; # Map from token to label
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ilabel = self.classify(type, value, context)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -185,11 +185,10 @@ class Parser(object):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; # No success finding a transition
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; raise ParseError(&amp;#34;bad input&amp;#34;, type, value, context)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- def classify(self, type: int, value: Optional[Text], context: Context) -&amp;gt; int:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ def classify(self, type: int, value: Text, context: Context) -&amp;gt; int:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;&amp;#34;&amp;#34;Turn a token into a label. (Internal)&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if type == token.NAME:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; # Keep a listing of all used names
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- assert value is not None
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; self.used_names.add(value)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; # Check for reserved words
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ilabel = self.grammar.keywords.get(value)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -201,12 +200,10 @@ class Parser(object):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return ilabel
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; def shift(
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- self, type: int, value: Optional[Text], newstate: int, context: Context
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ self, type: int, value: Text, newstate: int, context: Context
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ) -&amp;gt; None:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;&amp;#34;&amp;#34;Shift a token. (Internal)&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; dfa, state, node = self.stack[-1]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- assert value is not None
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- assert context is not None
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; rawnode: RawNode = (type, value, context, None)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; newnode = self.convert(self.grammar, rawnode)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if newnode is not None:
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Since mypyc injects runtime type checks, simplifying &lt;code&gt;Optional[Text]&lt;/code&gt; to &lt;code&gt;Text&lt;/code&gt; reduces
function call overhead. &lt;code&gt;value&lt;/code&gt; only needs to pass (in equivalent C code)
&lt;code&gt;isinstance(value, str)&lt;/code&gt;. This also has the neat side-effect of allowing me to remove some
asserts.&lt;/p&gt;
&lt;p&gt;Tightening up type annotations involving &lt;a href="https://docs.python.org/3/library/typing.html#typing.Any"target="_blank" rel="noopener"&gt;&lt;code&gt;typing.Any&lt;/code&gt;&lt;/a&gt; can be particularly
worthwhile as it forces the use of generic C code that can handle any kind of object. I
could only make this change once, but it&amp;rsquo;s better than nothing.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -54,14 +56,14 @@ class Driver(object):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; self.logger = logger
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; self.convert = convert
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- def parse_tokens(self, tokens: Iterable[Any], debug: bool = False) -&amp;gt; NL:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ def parse_tokens(self, tokens: Iterable[GoodTokenInfo], debug: bool = False) -&amp;gt; NL:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;&amp;#34;&amp;#34;Parse a series of tokens and return the syntax tree.&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; # XXX Move the prefix computation into a wrapper around tokenize.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; p = parse.Parser(self.grammar, self.convert)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; p.setup()
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; lineno = 1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; column = 0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4&gt;Marking everything Final&lt;span class="hx:absolute hx:-mt-20" id="marking-everything-final"&gt;&lt;/span&gt;
&lt;a href="#marking-everything-final" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;Avoiding calculating the same value over and over again is one of the most common
optimizations out there, and it&amp;rsquo;s for good reason, it&amp;rsquo;s usually easy to fix. BUT, with
mypyc we can take this further by using &lt;a href="https://docs.python.org/3/library/typing.html#typing.Final"target="_blank" rel="noopener"&gt;&lt;code&gt;typing.Final&lt;/code&gt;&lt;/a&gt;. Final variables
can often be injected at lookup sites at compile time, skipping the lookups at runtime!&lt;/p&gt;
&lt;p&gt;Let me show an example, let&amp;rsquo;s take this code and see what adding a single &lt;code&gt;Final&lt;/code&gt; does.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;SCALE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_y&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;SCALE&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Compiling it with mypyc, the C code for the &lt;code&gt;calculate_y&lt;/code&gt; function is .. well .. quite
long! Notice how much work has to be done to safely look up the global value at runtime.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;CPyDef_calculate_y&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cpy_r_x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cpy_r_r0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cpy_r_r1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cpy_r_r2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;CPyTagged&lt;/span&gt; &lt;span class="n"&gt;cpy_r_r3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cpy_r_r4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cpy_r_r5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cpy_r_r6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nl"&gt;CPyL0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cpy_r_r0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CPyStatic_globals&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cpy_r_r1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CPyStatics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="cm"&gt;/* &amp;#39;SCALE&amp;#39; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cpy_r_r2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CPyDict_GetItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpy_r_r0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cpy_r_r1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;unlikely&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpy_r_r2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;CPy_AddTraceback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;final.py&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;calculate_y&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CPyStatic_globals&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;CPyL4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="nl"&gt;CPyL1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;likely&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;PyLong_Check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpy_r_r2&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cpy_r_r3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CPyTagged_FromObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpy_r_r2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;CPy_TypeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;int&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cpy_r_r2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;cpy_r_r3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CPY_INT_TAG&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;CPy_DECREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpy_r_r2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;unlikely&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpy_r_r3&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;CPY_INT_TAG&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;CPy_AddTraceback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;final.py&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;calculate_y&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CPyStatic_globals&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;CPyL4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nl"&gt;CPyL2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cpy_r_r4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CPyTagged_StealAsObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpy_r_r3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cpy_r_r5&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;PyNumber_Multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpy_r_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cpy_r_r4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;CPy_DECREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpy_r_r4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;unlikely&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpy_r_r5&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;CPy_AddTraceback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;final.py&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;calculate_y&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CPyStatic_globals&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;CPyL4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nl"&gt;CPyL3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cpy_r_r5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nl"&gt;CPyL4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cpy_r_r6&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cpy_r_r6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;If we mark the &lt;code&gt;SCALE&lt;/code&gt; variable as Final, &lt;strong&gt;mypyc will notice that and inline the value&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Final&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;SCALE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Final&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_y&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;SCALE&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Just look at how much shorter this is!&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;CPyDef_calculate_y&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cpy_r_x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cpy_r_r0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cpy_r_r1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cpy_r_r2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nl"&gt;CPyL0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cpy_r_r0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CPyTagged_StealAsObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cpy_r_r1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;PyNumber_Multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpy_r_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cpy_r_r0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;CPy_DECREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpy_r_r0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;unlikely&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpy_r_r1&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;CPy_AddTraceback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;final.py&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;calculate_y&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CPyStatic_globals&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;CPyL2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nl"&gt;CPyL1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cpy_r_r1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nl"&gt;CPyL2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cpy_r_r2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cpy_r_r2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;If the value can&amp;rsquo;t be inlined, say because it involves a function call then mypyc will
just replace the globals dictionary lookup and instead use a C static (which is still
faster).&lt;/p&gt;
&lt;p&gt;Applying this optimization to Black yields changes like the following to &lt;code&gt;black.comments&lt;/code&gt;
and &lt;code&gt;black.parsing&lt;/code&gt;&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; respectively:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -12,11 +18,10 @@ from black.nodes import STANDALONE_COMMENT, WHITESPACE
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; # types
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; LN = Union[Leaf, Node]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-FMT_OFF = {&amp;#34;# fmt: off&amp;#34;, &amp;#34;# fmt:off&amp;#34;, &amp;#34;# yapf: disable&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-FMT_SKIP = {&amp;#34;# fmt: skip&amp;#34;, &amp;#34;# fmt:skip&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-FMT_PASS = {*FMT_OFF, *FMT_SKIP}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-FMT_ON = {&amp;#34;# fmt: on&amp;#34;, &amp;#34;# fmt:on&amp;#34;, &amp;#34;# yapf: enable&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+FMT_OFF: Final = {&amp;#34;# fmt: off&amp;#34;, &amp;#34;# fmt:off&amp;#34;, &amp;#34;# yapf: disable&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+FMT_SKIP: Final = {&amp;#34;# fmt: skip&amp;#34;, &amp;#34;# fmt:skip&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+FMT_PASS: Final = {*FMT_OFF, *FMT_SKIP}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+FMT_ON: Final = {&amp;#34;# fmt: on&amp;#34;, &amp;#34;# fmt:on&amp;#34;, &amp;#34;# yapf: enable&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; @dataclass
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -36,6 +36,11 @@ except ImportError:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; else:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ast3 = ast27 = ast
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+if sys.version_info &amp;gt;= (3, 8):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ TYPE_IGNORE_CLASSES: Final = (ast3.TypeIgnore, ast27.TypeIgnore, ast.TypeIgnore)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+else:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ TYPE_IGNORE_CLASSES: Final = (ast3.TypeIgnore, ast27.TypeIgnore)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; class InvalidInput(ValueError):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;&amp;#34;&amp;#34;Raised when input source code fails all parse attempts.&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -160,10 +165,7 @@ def stringify_ast(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; for field in sorted(node._fields): # noqa: F402
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; # TypeIgnore has only one field &amp;#39;lineno&amp;#39; which breaks this comparison
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- type_ignore_classes: Tuple[Type, ...] = (ast3.TypeIgnore, ast27.TypeIgnore)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- if sys.version_info &amp;gt;= (3, 8):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- type_ignore_classes += (ast.TypeIgnore,)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- if isinstance(node, type_ignore_classes):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ if isinstance(node, TYPE_IGNORE_CLASSES):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; break
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; try:
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;In total, this was the most common optimization I applied throughout this whole project.
It&amp;rsquo;s simple but effective!&lt;/p&gt;
&lt;h4&gt;Taking advantage of early binding&lt;span class="hx:absolute hx:-mt-20" id="taking-advantage-of-early-binding"&gt;&lt;/span&gt;
&lt;a href="#taking-advantage-of-early-binding" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;&lt;code&gt;Final&lt;/code&gt; is so fast because of early binding. What&amp;rsquo;s great is you can take advantage of
early binding with function calls too, assuming your code is static enough.&lt;/p&gt;
&lt;p&gt;Time for another example:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;tag&amp;gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;repr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;process_items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;3&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The function used to process the items isn&amp;rsquo;t known until call time, forcing mypyc to fall
back to the standard Python calling convention (albeit it does use the faster
&lt;a href="https://peps.python.org/pep-0590/"target="_blank" rel="noopener"&gt;vectorcall convention&lt;/a&gt; available for C functions). If I instead hardcode &lt;code&gt;tag&lt;/code&gt; in
&lt;code&gt;process_items&lt;/code&gt;, mypyc can &lt;strong&gt;call the C function directly&lt;/strong&gt; which involves way less work.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;tag&amp;gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;repr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;process_items&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;3&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; CPyL3: ;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cpy_r_r8 = CPyList_GetItemUnsafe(cpy_r_items, cpy_r_r3);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cpy_r_i = cpy_r_r8;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- PyObject *cpy_r_r9[1] = {cpy_r_i};
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- cpy_r_r10 = (PyObject **)&amp;amp;cpy_r_r9;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- cpy_r_r11 = _PyObject_Vectorcall(cpy_r_func, cpy_r_r10, 1, 0);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- if (unlikely(cpy_r_r11 == NULL)) {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ cpy_r_r9 = CPyDef_convert(cpy_r_i);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ CPy_DECREF(cpy_r_i);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ if (unlikely(cpy_r_r9 == NULL)) {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; CPy_AddTraceback(&amp;#34;early_binding.py&amp;#34;, &amp;#34;process_items&amp;#34;, 7, CPyStatic_globals);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; goto CPyL8;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Most of the time this isn&amp;rsquo;t possible because if your function calls are dynamic, it&amp;rsquo;s
probably because the code depends on it &amp;hellip; but keep it in mind.&lt;/p&gt;
&lt;p&gt;By sheer luck, I was able to replace two dynamic function calls with static calls in
&lt;code&gt;blib2to3.pgen2.parse.Parser&lt;/code&gt;, the very hot parser code!&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;diff --git a/src/blib2to3/pgen2/parse.py b/src/blib2to3/pgen2/parse.py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;index 6b03188..b5da4fa 100644
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;--- a/src/blib2to3/pgen2/parse.py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+++ b/src/blib2to3/pgen2/parse.py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -23,7 +23,7 @@ from typing import (
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Set,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; from blib2to3.pgen2.grammar import Grammar
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-from blib2to3.pytree import NL, Context, RawNode, Leaf, Node
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+from blib2to3.pytree import convert, NL, Context, RawNode, Leaf, Node
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -199,16 +206,13 @@ class Parser(object):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; raise ParseError(&amp;#34;bad token&amp;#34;, type, value, context)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return ilabel
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; def shift(self, type: int, value: Text, newstate: int, context: Context) -&amp;gt; None:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;&amp;#34;&amp;#34;Shift a token. (Internal)&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; dfa, state, node = self.stack[-1]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; rawnode: RawNode = (type, value, context, None)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- newnode = self.convert(self.grammar, rawnode)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- if newnode is not None:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- assert node[-1] is not None
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- node[-1].append(newnode)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ newnode = convert(self.grammar, rawnode)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ assert node[-1] is not None
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ node[-1].append(newnode)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; self.stack[-1] = (dfa, newstate, node)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; def push(self, type: int, newdfa: DFAS, newstate: int, context: Context) -&amp;gt; None:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; @@ -221,12 +225,11 @@ class Parser(object):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; def pop(self) -&amp;gt; None:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;&amp;#34;&amp;#34;Pop a nonterminal. (Internal)&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; popdfa, popstate, popnode = self.stack.pop()
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- newnode = self.convert(self.grammar, popnode)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- if newnode is not None:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- if self.stack:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- dfa, state, node = self.stack[-1]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- assert node[-1] is not None
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- node[-1].append(newnode)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- else:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- self.rootnode = newnode
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- self.rootnode.used_names = self.used_names
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ newnode = convert(self.grammar, popnode)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ if self.stack:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ dfa, state, node = self.stack[-1]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ assert node[-1] is not None
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ node[-1].append(newnode)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ else:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ self.rootnode = newnode
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ self.rootnode.used_names = self.used_names
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Also since &lt;code&gt;blib2to3.pytree.convert&lt;/code&gt; is guaranteed to return a non-None value, I could
drop a branch which was neat :)&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Detour: let&amp;rsquo;s build developer tooling!&lt;span class="hx:absolute hx:-mt-20" id="detour-lets-build-developer-tooling"&gt;&lt;/span&gt;
&lt;a href="#detour-lets-build-developer-tooling" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;If you&amp;rsquo;ve worked with me before you&amp;rsquo;ll know that I &lt;em&gt;love&lt;/em&gt; to write developer tooling to
make life easier. (Un)fortunately this project needed two bits of tooling that simply
didn&amp;rsquo;t exist at the time:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A benchmark suite&lt;/li&gt;
&lt;li&gt;A tool to compare two builds of Black behaviourally&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first one is pretty self-explanatory, I needed a good benchmark suite to make sure
this project would actually improve performance, and to also quantify the gains (important
when weighing optimizations).&lt;/p&gt;
&lt;p&gt;The second one is less clear, I effectively wanted &lt;a href="https://github.com/hauntsaninja/mypy_primer"target="_blank" rel="noopener"&gt;mypy-primer&lt;/a&gt;, but for Black:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://sichard.ca/media/mypy-primer-comment.png" alt="mypy-primer comment on python/mypy PR 12064 describing the impact of the change" loading="lazy" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/python/mypy/pull/12064"target="_blank" rel="noopener"&gt;mypy-primer comment on mypy PR #12064&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So I got to work creating &lt;a href="https://github.com/ichard26/blackbench"target="_blank" rel="noopener"&gt;blackbench&lt;/a&gt; and
&lt;a href="https://github.com/ichard26/black-mypyc-wheels/blob/c448ae49df7570dc2745eccd947625897f6541ce/diff_shades.py"target="_blank" rel="noopener"&gt;(the original) diff-shades&lt;/a&gt;. In hindsight, blackbench sucks and
needs a rewrite so I don&amp;rsquo;t want to go into too much detail about it, but the summary is
that it came with benchmarks for the following tasks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Formatting &lt;strong&gt;with&lt;/strong&gt; safety checks&lt;/li&gt;
&lt;li&gt;Formatting &lt;strong&gt;without&lt;/strong&gt; safety checks&lt;/li&gt;
&lt;li&gt;Blib2to3 parsing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;across quite a few inputs. The story is similar for the original diff-shades, it worked
and made it possible to verify mypyc didn&amp;rsquo;t change formatting, but its code was horrible
(and not to mention unmaintainable). It was bad enough that
&lt;a href="https://github.com/ichard26/diff-shades"target="_blank" rel="noopener"&gt;I rewrote the tool later on&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The TL;DR version of what diff-shades does is that it clones a bunch of projects, runs
Black on &amp;rsquo;em while recording the results. Then you use its other commands to analyze and
compare recordings.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Detour: does GCC help?&lt;span class="hx:absolute hx:-mt-20" id="detour-does-gcc-help"&gt;&lt;/span&gt;
&lt;a href="#detour-does-gcc-help" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;At this point I had found a workaround to the GCC
&lt;code&gt;array subscript 1 is above array bounds&lt;/code&gt; error and was curious to whether using GCC would
produce faster binaries. The answer turned out to be &lt;a href="https://github.com/ichard26/black-mypyc-wheels/issues/2#issuecomment-896357830"target="_blank" rel="noopener"&gt;&lt;em&gt;very much no&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Anyway, other than getting distracted by diff-shades, I looked into the gcc issue to
find a potential workaround. I found one, but it was useless 🙂 Collecting numbers for
both gcc-10 and clang showed that gcc fails in basically all departments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;takes almost twice as long to compile Black AND definitely uses 200%+ more memory&lt;/li&gt;
&lt;li&gt;had no meaningful difference in generated binary size all while being far more picky
about the C code it&amp;rsquo;s given&lt;/li&gt;
&lt;li&gt;oh and of course produced binaries that were around 8% slower 🙃&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m honestly happy that GCC failed to compile on that parser setup code, clang (for
Linux) is so much better.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3&gt;Results&lt;span class="hx:absolute hx:-mt-20" id="results"&gt;&lt;/span&gt;
&lt;a href="#results" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quick note&lt;/strong&gt;&lt;br&gt;
I also did some micro-optimizations like reordering if checks to hit the
common case first or replacing &lt;code&gt;x = x + 1&lt;/code&gt; with &lt;code&gt;x += 1&lt;/code&gt;, but to this day I don&amp;rsquo;t know
whether they actually had an impact.&lt;/p&gt;
&lt;p&gt;Additionally, I haven&amp;rsquo;t discussed the last
&lt;a href="https://github.com/psf/black/pull/2431/commits/c7de2eafb5d07033429ab3a18ce02ed093b645c5"target="_blank" rel="noopener"&gt;optimization round I did for src/black&lt;/a&gt;, but there&amp;rsquo;s nothing in that which
I haven&amp;rsquo;t covered yet.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Anyhow, these optimizations bumped the parsing speedup over interpreted from ~1.73x to
~1.9x. You can see the &lt;a href="https://github.com/psf/black/pull/2431/commits/f6a3e788bb8714e41fe0a4cc1ee2058b0a7cb3ac"target="_blank" rel="noopener"&gt;changes made here&lt;/a&gt; &lt;a href="https://github.com/psf/black/pull/2431/commits/911d0d8601318fcc04069f2af91a066e499f0db0"target="_blank" rel="noopener"&gt;and also here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For more detail, see the tables at the bottom for the &lt;code&gt;compiled-mypyc-preopt&lt;/code&gt; and
&lt;code&gt;compiled-mypyc&lt;/code&gt; columns &lt;a href="https://gist.github.com/ichard26/b996ccf410422b44fcd80fb158e05b0d"target="_blank" rel="noopener"&gt;in this report I compiled&lt;/a&gt;. It took a long time to
compile this report by the way, setting up a properly configured benchmark setup and
gathering multiple data samples is &lt;em&gt;very&lt;/em&gt; time consuming!&lt;/p&gt;
&lt;p&gt;In the end, I managed to increase compiled performance by an additional 10-15% which is
pretty nice! I was aiming for 25%, but in hindsight I might have been hoping for too much
🙂. Also yes, they were virtually useless when interpreted.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We&amp;rsquo;re nearly there, only &lt;a href="../compiling-black-with-mypyc-part-3/"&gt;Pt. 3 - Deployment&lt;/a&gt;
remains.&lt;/strong&gt; It&amp;rsquo;s shorter, believe me.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;In the end, I had to
&lt;a href="https://github.com/psf/black/pull/2431/commits/f5f1099dd9043e7bd62c3fd6d39dee1fd8ba458d"target="_blank" rel="noopener"&gt;revert this optimization so Black wouldn&amp;rsquo;t crash under PyPy&lt;/a&gt;, can&amp;rsquo;t
remember what the error was though&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Compiling Black with mypyc, Pt. 1 - Initial Steps</title><link>https://sichard.ca/blog/2022/05/compiling-black-with-mypyc-part-1/</link><pubDate>Tue, 31 May 2022 16:25:00 -0400</pubDate><guid>https://sichard.ca/blog/2022/05/compiling-black-with-mypyc-part-1/</guid><description>
&lt;p&gt;&lt;a href="https://github.com/psf/black/releases/tag/22.1.0"target="_blank" rel="noopener"&gt;Release 22.1.0 of Black&lt;/a&gt; was special, not only was it the first stable
version of Black, it was also the first release to ship with &lt;a href="https://mypyc.readthedocs.io/en/stable/introduction.html"target="_blank" rel="noopener"&gt;mypyc&lt;/a&gt; compiled wheels,
doubling performance&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; 🎉.&lt;/p&gt;
&lt;p&gt;The journey getting here took over two years, the creation of two new dev tools, and lots
and lots of headscratching. Let&amp;rsquo;s go down memory lane, shall we?&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;This is a long story so it&amp;rsquo;s broken up into three parts. Collectively this is the
&lt;em&gt;Compiling Black with mypyc&lt;/em&gt; series.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="."&gt;Pt. 1 - Initial Steps&lt;/a&gt; (you&amp;rsquo;re reading this one)&lt;/li&gt;
&lt;li&gt;&lt;a href="../compiling-black-with-mypyc-part-2/"&gt;Pt. 2 - Optimization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="../compiling-black-with-mypyc-part-3/"&gt;Pt. 3 - Deployment&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;While I&amp;rsquo;d love it if you would read this end to end, I totally understand if this ends
up in the &amp;ldquo;to read later&amp;rdquo; section of your bookmarks. I just hope you enjoy whatever you
do read!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;hellip; and yes it took me almost 6 months after deploying mypyc to write this.&lt;/p&gt;
&lt;h2&gt;Introduction to mypyc&lt;span class="hx:absolute hx:-mt-20" id="introduction-to-mypyc"&gt;&lt;/span&gt;
&lt;a href="#introduction-to-mypyc" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Before I dig into the story of how I integrated mypyc into Black, I want to explain what
mypyc is and show an example using it. &lt;strong&gt;mypyc is a &lt;a href="https://en.wikipedia.org/wiki/Source-to-source_compiler"target="_blank" rel="noopener"&gt;transcompiler&lt;/a&gt; converting typed
Python code into fast C extensions.&lt;/strong&gt; Being built on top of the &lt;a href="https://mypy.readthedocs.io/en/stable/"target="_blank" rel="noopener"&gt;mypy&lt;/a&gt; type checker, it
leverages standard type annotations&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; (unlike Cython).&lt;/p&gt;
&lt;p&gt;It has been used to compile mypy (and itself since mypyc comes with mypy) since 2019,
giving it a 4x performance boost over interpreted Python. According to the mypyc project:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Existing code with type annotations is often 1.5x to 5x faster when compiled. Code tuned
for mypyc can be 5x to 10x faster.&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It achieves these impressive speed ups by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Using the C-API directly, avoiding the CPython interpreter overhead.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leveraging &lt;em&gt;early binding&lt;/em&gt;, resolving called functions and names at compile-time,
skipping the expensive dictionary lookup at runtime.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Using optimized, type-specific primitives for many built-in functions and methods.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Using custom memory-efficient, unboxed representations for integers and booleans.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compiling regular classes to C extension classes. C extension classes use &lt;a href="https://en.wikipedia.org/wiki/Virtual_method_table"target="_blank" rel="noopener"&gt;vtables&lt;/a&gt; for
fast method calls and attribute access.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;hellip; and other optimizations I&amp;rsquo;ve left out for the sake of brevity.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Time for an example&lt;span class="hx:absolute hx:-mt-20" id="time-for-an-example"&gt;&lt;/span&gt;
&lt;a href="#time-for-an-example" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Interested? I sure hope so since it&amp;rsquo;s time for an example! To get started, create a
throwaway directory somewhere, a virtual environment, and install mypy. As of writing I&amp;rsquo;m
using mypy 0.931 on Ubuntu 20.04.03 with CPython 3.8.5.&lt;/p&gt;
&lt;p&gt;Next, let&amp;rsquo;s take a look at the code&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt; we&amp;rsquo;ll soon compile. It&amp;rsquo;s a simple program that
bruteforces its way to find all of the factor pairs for a given product. I mostly chose
this example because it&amp;rsquo;s very dear to my heart. It was the first &amp;ldquo;serious&amp;rdquo; program I
wrote while learning Python&lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt;. I originally wrote the same logic in code.org&amp;rsquo;s app lab
many years ago.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;answers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;factor&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;factor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;factor2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;factor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;answers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;factor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;factor2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;answers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;27_000_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;compute() took &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.1f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; milliseconds&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Once saved, run it noting the time it takes:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; python coefficient_finder.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;compute() took 1880.7 milliseconds
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Alright, it&amp;rsquo;s a bit slow, let&amp;rsquo;s see if mypyc can change that:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; mypyc coefficient_finder.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;running build_ext
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;creating build/temp.linux-x86_64-3.8
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;creating build/temp.linux-x86_64-3.8/build
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/home/ichard26/programming/webdev/blog/testing-grounds/venv/lib/python3.8/site-packages/mypyc/lib-rt -I/home/ichard26/programming/webdev/blog/testing-grounds/venv/include -I/opt/python3.8.5/include/python3.8 -c build/__native.c -o build/temp.linux-x86_64-3.8/build/__native.o -O3 -g1 -Werror -Wno-unused-function -Wno-unused-label -Wno-unreachable-code -Wno-unused-variable -Wno-unused-command-line-argument -Wno-unknown-warning-option -Wno-unused-but-set-variable
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;creating build/lib.linux-x86_64-3.8
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;gcc -pthread -shared build/temp.linux-x86_64-3.8/build/__native.o -o build/lib.linux-x86_64-3.8/coefficient_finder.cpython-38-x86_64-linux-gnu.so
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; python -c &lt;span class="s2"&gt;&amp;#34;import coefficient_finder&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;compute() took 331.6 milliseconds
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;And would you look at that, mypyc gave us a near 6x improvement over CPython! What&amp;rsquo;s neat
is that &lt;strong&gt;I didn&amp;rsquo;t even need to type all of the variables thanks to mypy&amp;rsquo;s type
inference&lt;/strong&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;And actually, since the &lt;code&gt;mypyc&lt;/code&gt; script is a simple wrapper that generates a setuptools
project and builds it in-place, you can peek into the &lt;code&gt;build&lt;/code&gt; directory and look at the
generated C code (should be in &lt;code&gt;__native.c&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Feel free to edit the program and play around with mypyc, seeing what little scripts it
can speed up. Pro-tip, to restore back to the interpreted version of your program, you can
delete the &lt;code&gt;.so&lt;/code&gt; file on Linux &amp;amp; MacOS or the &lt;code&gt;.pyd&lt;/code&gt; file on Windows. In this example I
got &lt;code&gt;coefficient_finder.cpython-38-x86_64-linux-gnu.so&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;It&amp;rsquo;s not a silver bullet&lt;span class="hx:absolute hx:-mt-20" id="its-not-a-silver-bullet"&gt;&lt;/span&gt;
&lt;a href="#its-not-a-silver-bullet" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;There&amp;rsquo;s several downsides to using mypyc. While the project aims for excellent
compatibility with CPython, there are some major deviations worth noting:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Types are enforced at runtime and violations &lt;strong&gt;will&lt;/strong&gt; cause a TypeError (this makes
using &lt;code&gt;typing.Any&lt;/code&gt; quite dangerous).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compiled modules can&amp;rsquo;t be run directly, you have to import &amp;rsquo;em instead.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Monkeypatching anything compiled is unlikely to work as compiled code skips many of the
runtime &lt;code&gt;__dict__&lt;/code&gt; lookups normal Python does.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Assignments to class and instance namespaces will either error or do nothing, in
particular you can&amp;rsquo;t add previously undeclared attributes later on.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Standard multiple inheritance isn&amp;rsquo;t supported.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Profilers (e.g. CProfile), debuggers (e.g. pdb), and tracing hooks (e.g. coverage) won&amp;rsquo;t
work as the C code doesn&amp;rsquo;t trigger the relevant hooks.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are more details available in the official
&lt;a href="https://mypyc.readthedocs.io/en/stable/differences_from_python.html"target="_blank" rel="noopener"&gt;mypyc documentation here&lt;/a&gt; and &lt;a href="https://mypyc.readthedocs.io/en/stable/native_classes.html"target="_blank" rel="noopener"&gt;also here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;black + mypyc: Initial steps&lt;span class="hx:absolute hx:-mt-20" id="black--mypyc-initial-steps"&gt;&lt;/span&gt;
&lt;a href="#black--mypyc-initial-steps" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I wasn&amp;rsquo;t the first person to integrate mypyc into Black; way back in September 2019,
&lt;a href="https://github.com/msullivan"target="_blank" rel="noopener"&gt;@msullivan&lt;/a&gt; &lt;a href="https://github.com/psf/black/pull/1009"target="_blank" rel="noopener"&gt;opened a PR getting Black ready&lt;/a&gt; for compilation.
Unfortunately, as is typical in open source projects, no one took the half-completed work
and pushed it to completion &amp;hellip; for almost two years.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You might be wondering why performance even matters, well clearly it mattered a lot
since &lt;a href="https://github.com/psf/black/issues/366"target="_blank" rel="noopener"&gt;GH-366&lt;/a&gt; was opened in June 2018! The TL;DR is that for environments where Black
is &lt;strong&gt;ran on save automatically, the more responsive it is the better&lt;/strong&gt; as less time is
spent in a laggy editor window.&lt;/p&gt;
&lt;p&gt;Start up time is important too given imports are costly (it turns out mypyc can reduce
import time too!), but this issue was explicitly about formatting throughput.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Given I first publicly announced my work finishing up the project on July 4th 2021, I
sadly don&amp;rsquo;t remember why I decided to pick it up. All I remember was fighting mypyc
compile-time type errors and fighting an outdated gcc&amp;hellip; well OK I do remember I was
looking to learn more about type systems and C development in general, but that&amp;rsquo;s it, I
promise!&lt;/p&gt;
&lt;p&gt;First off, as of writing mypyc has a bug (&lt;a href="https://github.com/mypyc/mypyc/issues/885"target="_blank" rel="noopener"&gt;mypyc#885&lt;/a&gt;) where its boxing &amp;amp; unboxing code
triggers &lt;code&gt;array subscript 1 is above array bounds&lt;/code&gt; on
&lt;a href="https://github.com/psf/black/blob/8ea641eed5b9540287a8e9a9afa1458b72b9b630/src/blib2to3/pgen2/parse.py#L117-L139"target="_blank" rel="noopener"&gt;&lt;code&gt;blib2to3.pgen2.parse.Parser.setup&lt;/code&gt;&lt;/a&gt;. I simplified the reproduction case to
the following:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;RawNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# This is what ticks off gcc.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;newnode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RawNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&amp;hellip; yeah, I know this makes mypyc look fragile. While it&amp;rsquo;s not entirely stupid to use it
in production (because I did so), you&amp;rsquo;d be right in pointing out it&amp;rsquo;s alpha quality and
needs careful testing. And to be fair, poor past me didn&amp;rsquo;t know just how many issues I&amp;rsquo;d
soon face. All I can hope is that this work will help mypyc improve ❀&lt;/p&gt;
&lt;p&gt;Anyway I switched to clang and this issue disappeared &amp;hellip;&lt;/p&gt;
&lt;p&gt;Trying to compile &lt;code&gt;black&lt;/code&gt; and &lt;code&gt;blib2to3&lt;/code&gt;&lt;sup id="fnref:6"&gt;&lt;a href="#fn:6" class="footnote-ref" role="doc-noteref"&gt;6&lt;/a&gt;&lt;/sup&gt; initially seemed like a small task with just
these type errors:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; python setup.py --use-mypyc bdist_wheel
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Parsed and typechecked in 8.566s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Compiled to C in 0.000s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;src/black/parsing.py:30:9: error: Cannot assign multiple modules to name &amp;#34;ast3&amp;#34; without explicit &amp;#34;types.ModuleType&amp;#34; annotation
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;src/black/parsing.py:30:9: error: Cannot assign multiple modules to name &amp;#34;ast27&amp;#34; without explicit &amp;#34;types.ModuleType&amp;#34; annotation
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;src/black/parsing.py:146:13: error: Incompatible types in assignment (expression has type &amp;#34;Tuple[Type[typed_ast.ast3.TypeIgnore], Type[typed_ast.ast27.TypeIgnore], Type[_ast.TypeIgnore]]&amp;#34;, variable has type &amp;#34;Tuple[Type[typed_ast.ast3.TypeIgnore], Type[typed_ast.ast27.TypeIgnore]]&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The ones about &lt;code&gt;Cannot assign multiple modules to name ...&lt;/code&gt; are valid if annoying, back
then the code in &lt;code&gt;black.parsing&lt;/code&gt; was pretty dynamic. The other one was just that mypy
under &amp;ldquo;mypyc super-strict mode&amp;rdquo; won&amp;rsquo;t infer a variable to be a variable-length tuple even
if it&amp;rsquo;s extended right after:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stringify_ast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ast3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ast27&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AST&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Iterator&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;Simple visitor generating strings to compare ASTs by content.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fixup_ast_constants&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__class__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;(&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_fields&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="c1"&gt;# noqa: F402&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# TypeIgnore has only one field &amp;#39;lineno&amp;#39; which breaks this comparison&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;type_ignore_classes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ast3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TypeIgnore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ast27&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TypeIgnore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version_info&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;type_ignore_classes&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TypeIgnore&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The fix was to add &lt;code&gt;Tuple[Type, ...]&lt;/code&gt;.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;type_ignore_classes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Type&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="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ast3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TypeIgnore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ast27&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TypeIgnore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Now, the codebase was able to type check even under &amp;ldquo;mypyc super-strict mode,&amp;rdquo; but
&lt;em&gt;obviously&lt;/em&gt; that just meant I had another class of fires to deal with, *compiler
crashes*!&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; python setup.py --use-mypyc bdist_wheel
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Parsed and typechecked in 8.929s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Compiling cache, strings, black, debug, parsing, brackets, lines, files, literals, conv, trans, linegen, numerics, nodes, const, pgen, pygram, output, token, concurrency, parse, report, mode, driver, grammar, tokenize, pytree, rusty, comments
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Traceback (most recent call last):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; File &amp;#34;mypyc/irbuild/builder.py&amp;#34;, line 169, in accept
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; File &amp;#34;mypy/nodes.py&amp;#34;, line 950, in accept
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; File &amp;#34;mypyc/irbuild/visitor.py&amp;#34;, line 104, in visit_class_def
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; File &amp;#34;mypyc/irbuild/classdef.py&amp;#34;, line 137, in transform_class_def
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; File &amp;#34;mypyc/irbuild/classdef.py&amp;#34;, line 388, in generate_attr_defaults
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; File &amp;#34;mypyc/ir/class_ir.py&amp;#34;, line 174, in attr_type
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; File &amp;#34;mypyc/ir/class_ir.py&amp;#34;, line 171, in attr_details
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;src/black/trans.py:187: KeyError: &amp;#34;&amp;#39;CustomSplitMapMixin&amp;#39; has no attribute &amp;#39;_Key&amp;#39;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This issue was just due to missing &lt;code&gt;ClassVar&lt;/code&gt; declarations for attributes that are
functionally class-only attributes. The fix was pretty easy :)&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+@trait
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; class CustomSplitMapMixin:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; This mixin class is used to map merged strings to a sequence of
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -191,8 +204,10 @@ class CustomSplitMapMixin:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; the resultant substrings go over the configured max line length.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- _Key = Tuple[StringID, str]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- _CUSTOM_SPLIT_MAP: Dict[_Key, Tuple[CustomSplit, ...]] = defaultdict(tuple)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ _Key: ClassVar = Tuple[StringID, str]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ _CUSTOM_SPLIT_MAP: ClassVar[Dict[_Key, Tuple[CustomSplit, ...]]] = defaultdict(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ tuple
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ )
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Do you see that &lt;code&gt;@trait&lt;/code&gt; addition? well &amp;hellip; mypyc does in fact &lt;strong&gt;support a limited form of
multiple inheritance called &lt;em&gt;traits&lt;/em&gt;&lt;/strong&gt;. They&amp;rsquo;re basically mixins. As noted in the
&lt;a href="https://mypyc.readthedocs.io/en/latest/using_type_annotations.html#trait-types"target="_blank" rel="noopener"&gt;mypyc documentation&lt;/a&gt;, they shouldn&amp;rsquo;t be instantiated or subclass non-traits.&lt;/p&gt;
&lt;p&gt;And as usual, fixing this crash revealed *even* *more* *issues*, albeit mypyc
specific this time:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; python setup.py --use-mypyc bdist_wheel
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Parsed and typechecked in 8.122s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Compiling pgen, parse, parsing, mode, pytree, token, debug, const, lines, numerics, rusty, conv, comments, files, trans, cache, black, grammar, output, brackets, tokenize, linegen, pygram, report, literals, nodes, strings, concurrency, driver
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Compiled to C in 0.970s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;src/black/trans.py:250: error: Non-trait bases must appear first in parent list
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;src/black/trans.py:934: error: Non-trait bases must appear first in parent list
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;src/black/trans.py:1433: error: Non-trait bases must appear first in parent list
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;src/black/brackets.py:52: error: Inheriting from most builtin types is unimplemented
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;src/black/files.py:52: warning: Treating generator comprehension as list
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;src/black/trans.py:746: warning: Unsupported default attribute value
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;src/black/trans.py:1831: warning: Unsupported default attribute value
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;src/blib2to3/pgen2/tokenize.py:430: error: Local variable &amp;#34;stashed&amp;#34; has inferred type None; add an annotation
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;src/black/nodes.py:442: error: Local variable &amp;#34;stop_after&amp;#34; has inferred type None; add an annotation
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;src/black/nodes.py:443: error: Local variable &amp;#34;last&amp;#34; has inferred type None; add an annotation
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;These were pretty straightforward to fix and sometimes even improved code clarity, forcing
me to add type annotations in complex code.&lt;/p&gt;
&lt;h3&gt;More changes were necessary than expected&lt;span class="hx:absolute hx:-mt-20" id="more-changes-were-necessary-than-expected"&gt;&lt;/span&gt;
&lt;a href="#more-changes-were-necessary-than-expected" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;All of this was only to get the codebase to type check and not crash the compiler. Getting
the built binary to not crash at runtime needed more work. Some of these changes were
improvements, like this one which involved an incorrect type annotation:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;diff --git a/src/black/__init__.py b/src/black/__init__.py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;index 8e2123d..6998c1e 100644
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;--- a/src/black/__init__.py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+++ b/src/black/__init__.py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -359,7 +359,7 @@ def main(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; experimental_string_processing: bool,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; quiet: bool,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; verbose: bool,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- required_version: str,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ required_version: Optional[str],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; include: Pattern,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; exclude: Optional[Pattern],
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; extend_exclude: Optional[Pattern],
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;This goes to show that while the strict runtime type checks can be very annoying, they
do have value beyond memory safety.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;Sad and frustrating changes&lt;span class="hx:absolute hx:-mt-20" id="sad-and-frustrating-changes"&gt;&lt;/span&gt;
&lt;a href="#sad-and-frustrating-changes" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;&amp;hellip; but others were just sad or annoying (mixing dataclasses with anything remotely fancy
breaks mypyc).&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;diff --git a/src/black/linegen.py b/src/black/linegen.py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;index 76b553a..1691cc5 100644
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;--- a/src/black/linegen.py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+++ b/src/black/linegen.py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -40,7 +38,8 @@ class CannotSplit(CannotTransform):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;&amp;#34;&amp;#34;A readable split that fits the allotted line length is impossible.&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-@dataclass
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+# This isn&amp;#39;t a dataclass because @dataclass + Generic breaks mypyc.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+# See also https://github.com/mypyc/mypyc/issues/827.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; class LineGenerator(Visitor[Line]):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;&amp;#34;&amp;#34;Generates reformatted Line objects. Empty lines are not emitted.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -48,9 +47,11 @@ class LineGenerator(Visitor[Line]):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; in ways that will no longer stringify to valid Python code on the tree.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- mode: Mode
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- remove_u_prefix: bool = False
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- current_line: Line = field(init=False)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ def __init__(self, mode: Mode, remove_u_prefix: bool = False) -&amp;gt; None:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ self.mode = mode
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ self.remove_u_prefix = remove_u_prefix
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ self.current_line: Line
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ self.__post_init__()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; def line(self, indent: int = 0) -&amp;gt; Iterator[Line]:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;&amp;#34;&amp;#34;Generate a line.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;diff --git a/src/black/trans.py b/src/black/trans.py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;index 023dcd3..d918ef1 100644
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;--- a/src/black/trans.py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+++ b/src/black/trans.py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -62,7 +71,6 @@ def TErr(err_msg: str) -&amp;gt; Err[CannotTransform]:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return Err(cant_transform)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-@dataclass # type: ignore
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; class StringTransformer(ABC):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; An implementation of the Transformer protocol that relies on its
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -90,9 +98,13 @@ class StringTransformer(ABC):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; as much as possible.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- line_length: int
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- normalize_strings: bool
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- __name__ = &amp;#34;StringTransformer&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ __name__: Final = &amp;#34;StringTransformer&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ # Ideally this would be a dataclass, but unfortunately mypyc breaks when used with
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ # `abc.ABC`.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ def __init__(self, line_length: int, normalize_strings: bool) -&amp;gt; None:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ self.line_length = line_length
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ self.normalize_strings = normalize_strings
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4&gt;Hackiness += 10000&lt;span class="hx:absolute hx:-mt-20" id="hackiness--10000"&gt;&lt;/span&gt;
&lt;a href="#hackiness--10000" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;One change stood out as the most hacky&lt;sup id="fnref:7"&gt;&lt;a href="#fn:7" class="footnote-ref" role="doc-noteref"&gt;7&lt;/a&gt;&lt;/sup&gt; code I&amp;rsquo;ve probably ever written so far:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;diff --git a/src/black/linegen.py b/src/black/linegen.py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;index 76b553a..1691cc5 100644
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;--- a/src/black/linegen.py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+++ b/src/black/linegen.py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -335,7 +336,9 @@ def transform_line(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; transformers = [left_hand_split]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; else:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- def rhs(line: Line, features: Collection[Feature]) -&amp;gt; Iterator[Line]:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ def _rhs(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ self: object, line: Line, features: Collection[Feature]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ ) -&amp;gt; Iterator[Line]:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;&amp;#34;&amp;#34;Wraps calls to `right_hand_split`.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; The calls increasingly `omit` right-hand trailers (bracket pairs with
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -362,6 +365,11 @@ def transform_line(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; line, line_length=mode.line_length, features=features
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ # HACK: functions (like rhs) compiled by mypyc don&amp;#39;t retain their __name__
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ # attribute which is needed in `run_transformer` further down. Unfortunately
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ # a nested class breaks mypyc too. So a class must be created via type ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ rhs = type(&amp;#34;rhs&amp;#34;, (), {&amp;#34;__call__&amp;#34;: _rhs})()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if mode.experimental_string_processing:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if line.inside_brackets:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; transformers = [
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I also had to change the code accessing &lt;code&gt;__name__&lt;/code&gt; to do so via &lt;code&gt;__class__&lt;/code&gt; so this hack
could work. Minor correction though, top-level compiled functions still have &lt;code&gt;__name__&lt;/code&gt;,
it&amp;rsquo;s just nested functions that don&amp;rsquo;t have &amp;rsquo;em&lt;sup id="fnref:8"&gt;&lt;a href="#fn:8" class="footnote-ref" role="doc-noteref"&gt;8&lt;/a&gt;&lt;/sup&gt;. I improved this comment before this
landed.&lt;/p&gt;
&lt;h4&gt;The least bad workaround&lt;span class="hx:absolute hx:-mt-20" id="the-least-bad-workaround"&gt;&lt;/span&gt;
&lt;a href="#the-least-bad-workaround" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;I remember getting an impromptu &amp;ldquo;how to read mypyc-generated C code&amp;rdquo; lesson from
&lt;a href="https://github.com/JelleZijlstra"target="_blank" rel="noopener"&gt;@JelleZijlstra&lt;/a&gt; &lt;sup id="fnref:9"&gt;&lt;a href="#fn:9" class="footnote-ref" role="doc-noteref"&gt;9&lt;/a&gt;&lt;/sup&gt;. I was trying to debug this runtime crash in this &lt;code&gt;black.trans&lt;/code&gt;
class:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StringParser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; [snipped]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;DEFAULT_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# String Parser States&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;START&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;DOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;PERCENT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;SINGLE_FMT_ARG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;LPAR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;RPAR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;DONE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Lookup Table for Next State&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_goto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ParserState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NodeType&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;ParserState&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;snipped&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;snipped&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python3" data-lang="python3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Traceback&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;stdin&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;test2.py&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;START&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DEFAULT_TOKEN&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;DONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ne"&gt;KeyError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;DEFAULT_TOKEN&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;It turns out the generated code wanted to access the &lt;code&gt;DEFAULT_TOKEN&lt;/code&gt; attribute from the
globals dict, predictably blowing up.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cpy_r_r146&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CPyStatic_globals&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cpy_r_r147&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CPyStatics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="cm"&gt;/* &amp;#39;DEFAULT_TOKEN&amp;#39; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cpy_r_r148&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CPyDict_GetItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpy_r_r146&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cpy_r_r147&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;If this scares you, don&amp;rsquo;t fret, it&amp;rsquo;s not impossible to read. First, the globals dict is
assigned to &lt;code&gt;cpy_r_r146&lt;/code&gt;, then the name (or key) we&amp;rsquo;re looking up is read and stored in
&lt;code&gt;cpy_r_r147&lt;/code&gt;. Finally, &lt;code&gt;cpy_r_r146&lt;/code&gt; and &lt;code&gt;cpy_r_r147&lt;/code&gt; are passed to the &lt;code&gt;CPyDict_GetItem&lt;/code&gt;
function.&lt;/p&gt;
&lt;p&gt;Effectively, the buggy C code was trying to do this (unnecessary lines were replaced with
&lt;code&gt;...&lt;/code&gt;):&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StringParser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;DEFAULT_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;START&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;DONE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;__var_146&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;__var_147&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;DEFAULT_TOKEN&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;__var_148&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;var_146&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;var_147&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_goto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;START&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;__var_148&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;DONE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The root issue (&lt;a href="https://github.com/mypyc/mypyc/issues/862"target="_blank" rel="noopener"&gt;mypyc#862&lt;/a&gt;) is that mypyc doesn&amp;rsquo;t understand class level scoping &amp;hellip; or
that negative integers are constants (using a positive integer fixed the crash). I
embedded the date I workarounded this issue, I think it&amp;rsquo;s a pretty cute historical
codebase quirk ❀&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;@@ -1811,20 +1826,20 @@ class StringParser:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ```
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- DEFAULT_TOKEN = -1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ DEFAULT_TOKEN: Final = 20210605
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;em&gt;Phew&lt;/em&gt;, it&amp;rsquo;s faster&lt;span class="hx:absolute hx:-mt-20" id="phew-its-faster"&gt;&lt;/span&gt;
&lt;a href="#phew-its-faster" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;It was at this time I finally posted the
&lt;a href="https://github.com/psf/black/issues/366#issuecomment-855306152"target="_blank" rel="noopener"&gt;first set of benchmark numbers&lt;/a&gt;, they weren&amp;rsquo;t scientific or reliable in
any way, but it did prove mypyc was still a worthwhile way to improve performance.
Formatting the Black codebase (so meta, I know) with compiled Black yielded a performance
gain of 1.82x, impressive for the short amount of time I had put in!&lt;/p&gt;
&lt;p&gt;I had to verify this experimental build wasn&amp;rsquo;t completely unstable though. I couldn&amp;rsquo;t just
run the test suite with compiled Black installed, some tests use mocks which trigger the
strict type checks mypyc does at runtime. Sadly, I had to mark these tests as straight up
incompatible so they could be skipped.&lt;/p&gt;
&lt;h3&gt;It&amp;rsquo;s still alpha software&lt;span class="hx:absolute hx:-mt-20" id="its-still-alpha-software"&gt;&lt;/span&gt;
&lt;a href="#its-still-alpha-software" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;As of writing mypyc is still alpha software with rough edges everywhere, I already showed
quite a few of them above, but there were more 😥 You can read the rest in this
&lt;a href="https://github.com/mypyc/mypyc/issues/886"target="_blank" rel="noopener"&gt;integration report&lt;/a&gt; I filed in the mypyc issue tracker.&lt;/p&gt;
&lt;p&gt;And I know, y&amp;rsquo;all are wondering why Black didn&amp;rsquo;t use Cython, well I&amp;rsquo;m not sure as I wasn&amp;rsquo;t
around in 2019 when &lt;a href="https://github.com/psf/black/issues/366#issuecomment-490153026"target="_blank" rel="noopener"&gt;@ambv first suggested using mypyc&lt;/a&gt;. Eitherway
I didn&amp;rsquo;t look into Cython because at the time as it seemed like I only had the final
scraps left and didn&amp;rsquo;t have too much work to do &amp;hellip; which was both right and wrong
depending on how you look at it.&lt;/p&gt;
&lt;p&gt;Could Black be even faster if we had used Cython? Probably, but that would have required
learning C which would have taken quite a while.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Anyway, it&amp;rsquo;s time for &lt;a href="../compiling-black-with-mypyc-part-2/"&gt;Pt. 2 - Optimization&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;When I first landed the relevant PR it was an overall 2x improvement, but once Jelle
added a stability hotfix the &lt;em&gt;effective&lt;/em&gt; speedup for files that were changed is 50%.
If you&amp;rsquo;re formatting a bunch of already well formatted files, the speedup is still 2x&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;There&amp;rsquo;s too many typing related PEPs to copy and paste, but there&amp;rsquo;s an up to date list
here: &lt;a href="https://docs.python.org/3/library/typing.html#relevant-peps"target="_blank" rel="noopener"&gt;https://docs.python.org/3/library/typing.html#relevant-peps&lt;/a&gt;&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;&lt;a href="https://mypyc.readthedocs.io/en/stable/introduction.html#introduction"target="_blank" rel="noopener"&gt;https://mypyc.readthedocs.io/en/stable/introduction.html#introduction&lt;/a&gt;&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;I am well aware I don&amp;rsquo;t need to import &lt;code&gt;List&lt;/code&gt; or &lt;code&gt;Tuple&lt;/code&gt; from &lt;code&gt;typing&lt;/code&gt; anymore. I&amp;rsquo;m
just keeping my code examples compatible with older versions of Python.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;Well not quite exactly this, it was a crappier solution with a time complexity of
O(n²) instead of O(n), oh and of course my code style was less than ideal and I didn&amp;rsquo;t
use type annotations, but let&amp;rsquo;s not go there :)&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:6"&gt;
&lt;p&gt;It&amp;rsquo;s a fork of lib2to3, see
&lt;a href="https://github.com/psf/black/blob/1e557184b0a9f43bfbff862669966bc5328517e9/src/blib2to3/README"target="_blank" rel="noopener"&gt;https://github.com/psf/black/blob/1e557184b0a9f43bfbff862669966bc5328517e9/src/blib2to3/README&lt;/a&gt;
for why.&amp;#160;&lt;a href="#fnref:6" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:7"&gt;
&lt;p&gt;What&amp;rsquo;s funny is that the PR which added this &lt;code&gt;__name__&lt;/code&gt; check had a review noting the
hackiness of reading the attribute in the first place. It was dismissed as it would
have taken too much effort to properly implement the required logic, turns out we got
this fun code in return!&amp;#160;&lt;a href="#fnref:7" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:8"&gt;
&lt;p&gt;It&amp;rsquo;s because internally mypyc implements nested functions as callable classes&amp;#160;&lt;a href="#fnref:8" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:9"&gt;
&lt;p&gt;Reading the results searching &lt;code&gt;from:ichard26#4772 in:black-formatter mypyc&lt;/code&gt; in the
Python Discord server nets some interesting discussion and history :p&amp;#160;&lt;a href="#fnref:9" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item></channel></rss>