<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Packaging | Bas Nijholt</title><link>https://www.nijho.lt/tag/packaging/</link><atom:link href="https://www.nijho.lt/tag/packaging/index.xml" rel="self" type="application/rss+xml"/><description>Packaging</description><generator>Hugo</generator><language>en-us</language><copyright>© 2026</copyright><lastBuildDate>Wed, 11 Feb 2026 00:00:00 +0000</lastBuildDate><image><url>https://www.nijho.lt/media/icon_hu_408a8f6cef43e327.png</url><title>Packaging</title><link>https://www.nijho.lt/tag/packaging/</link></image><item><title>Auto-installing extras at runtime so users never have to think about dependencies</title><link>https://www.nijho.lt/post/auto-install-extras/</link><pubDate>Wed, 11 Feb 2026 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/auto-install-extras/</guid><description>&lt;details class="toc" open&gt;&lt;summary&gt;Table of contents&lt;/summary&gt;&lt;nav id="TableOfContents"&gt;&#10; &lt;ul&gt;&#10; &lt;li&gt;&lt;a href="#1-from-one-command-to-a-dependency-maze"&gt;1. From one command to a dependency maze&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#2-why-just-use-extras-breaks-down"&gt;2. Why &amp;ldquo;just use extras&amp;rdquo; breaks down&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#3-the-decorator"&gt;3. The decorator&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#4-checking-without-importing"&gt;4. Checking without importing&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#5-the-metadata-bridge"&gt;5. The metadata bridge&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#6-three-environments-three-strategies"&gt;6. Three environments, three strategies&lt;/a&gt;&#10; &lt;ul&gt;&#10; &lt;li&gt;&lt;a href="#a-uv-tool-install--the-persistent-environment"&gt;A) &lt;code&gt;uv tool install&lt;/code&gt; — the persistent environment&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#b-uvx--the-ephemeral-environment"&gt;B) &lt;code&gt;uvx&lt;/code&gt; — the ephemeral environment&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#c-regular-virtualenv-pipx-or-system-install"&gt;C) Regular virtualenv, &lt;code&gt;pipx&lt;/code&gt;, or system install&lt;/a&gt;&lt;/li&gt;&#10; &lt;/ul&gt;&#10; &lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#7-preventing-infinite-re-execution-loops"&gt;7. Preventing infinite re-execution loops&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#8-what-users-actually-see"&gt;8. What users actually see&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#9-keeping-everything-in-sync"&gt;9. Keeping everything in sync&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#10-why-uv-made-this-practical"&gt;10. Why uv made this practical&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#11-tradeoffs"&gt;11. Tradeoffs&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#12-not-dependency-injection"&gt;12. Not dependency injection&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#13-conclusion"&gt;13. Conclusion&lt;/a&gt;&lt;/li&gt;&#10; &lt;/ul&gt;&#10;&lt;/nav&gt;&lt;/details&gt;&#10;&#10;&lt;h2 id="1-from-one-command-to-a-dependency-maze"&gt;1. From one command to a dependency maze &lt;a class="anchor" href="#1-from-one-command-to-a-dependency-maze" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://github.com/basnijholt/agent-cli" target="_blank" rel="noopener"&gt;&lt;code&gt;agent-cli&lt;/code&gt;&lt;/a&gt; started as a simple thing.&#10;Voice in, text out.&#10;It connected to a &lt;a href="https://github.com/rhasspy/wyoming" target="_blank" rel="noopener"&gt;Wyoming&lt;/a&gt; Whisper server for speech-to-text and a Piper server for text-to-speech, basically a local, open-source Siri triggered from terminal hotkeys.&#10;I wrote about the early version in my &lt;a href="https://www.nijho.lt/post/local-ai-journey/"&gt;local AI journey&lt;/a&gt; post.&lt;/p&gt;&#10;&lt;p&gt;And then, as tends to happen with side projects I use every day, I kept adding things.&lt;/p&gt;&#10;&lt;p&gt;First a RAG proxy with ChromaDB, so I could chat with my documents.&#10;Then a long-term memory system, so conversations could remember context across sessions.&#10;Server commands that run Kokoro TTS or Whisper locally, optimized for whatever hardware you have—Apple Silicon gets MLX, NVIDIA gets CTranslate2.&#10;Voice activity detection with Silero VAD.&#10;Audio speed adjustment.&#10;An &lt;a href="https://www.nijho.lt/post/parallel-agentic-coding/"&gt;agent orchestration system&lt;/a&gt;.&#10;&lt;code&gt;systemd&lt;/code&gt;/&lt;code&gt;launchd&lt;/code&gt; service management for running background daemons.&lt;/p&gt;&#10;&lt;p&gt;Each of these features pulled in wildly different dependency trees.&#10;&lt;code&gt;torch&lt;/code&gt; and &lt;code&gt;transformers&lt;/code&gt; for GPU-accelerated Whisper.&#10;&lt;code&gt;sounddevice&lt;/code&gt; and &lt;code&gt;numpy&lt;/code&gt; for microphone access.&#10;&lt;code&gt;chromadb&lt;/code&gt; and &lt;code&gt;onnxruntime&lt;/code&gt; for vector storage.&#10;&lt;code&gt;kokoro&lt;/code&gt; with its entire spacy pipeline for neural TTS.&lt;/p&gt;&#10;&lt;p&gt;Today the project has &lt;strong&gt;13 optional extras&lt;/strong&gt;.&#10;The tool became much more useful, but dependency UX got progressively worse with every feature.&lt;/p&gt;&#10;&lt;aside class="callout callout-note"&gt;&lt;svg class="ico" viewBox="0 0 256 256" aria-hidden="true"&gt;&lt;path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"/&gt;&lt;/svg&gt;&lt;div&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I moved from &amp;ldquo;users choose extras upfront&amp;rdquo; to &lt;strong&gt;runtime optional dependency resolution&lt;/strong&gt;.&#10;Commands declare required extras with &lt;code&gt;@requires_extras(...)&lt;/code&gt;; if something is missing, &lt;code&gt;agent-cli&lt;/code&gt; installs it in the right environment and re-runs the command automatically.&#10;The user just types &lt;code&gt;agent-cli transcribe&lt;/code&gt; and everything works.&lt;/div&gt;&lt;/aside&gt;&#10;&lt;h2 id="2-why-just-use-extras-breaks-down"&gt;2. Why &amp;ldquo;just use extras&amp;rdquo; breaks down &lt;a class="anchor" href="#2-why-just-use-extras-breaks-down" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Python&amp;rsquo;s &lt;code&gt;optional-dependencies&lt;/code&gt; in &lt;code&gt;pyproject.toml&lt;/code&gt; is the standard mechanism for this, and it&amp;rsquo;s the right packaging primitive.&#10;You define groups, users install with &lt;code&gt;pip install agent-cli[rag]&lt;/code&gt;.&#10;On paper, this is perfectly reasonable.&lt;/p&gt;&#10;&lt;p&gt;In practice, it falls apart when you have 13 of them:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;optional-dependencies&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;audio&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;numpy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;sounddevice&amp;gt;=0.4.6&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;agent-cli[wyoming]&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;llm&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;pydantic-ai-slim[openai,google,duckduckgo,vertexai]&amp;gt;=0.1.1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;rag&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;pydantic-ai-slim[openai,google]&amp;gt;=0.1.1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;chromadb&amp;gt;=0.4.22&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;memory&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;pydantic-ai-slim[openai,google]&amp;gt;=0.1.1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;chromadb&amp;gt;=0.4.22&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;vad&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;onnxruntime&amp;gt;=1.16.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;faster-whisper&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;faster-whisper&amp;gt;=1.0.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;mlx-whisper&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;mlx-whisper&amp;gt;=0.4.0; sys_platform == &amp;#39;darwin&amp;#39; and platform_machine == &amp;#39;arm64&amp;#39;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;kokoro&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;kokoro&amp;gt;=0.9.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;soundfile&amp;gt;=0.12.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;piper&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;piper-tts&amp;gt;=1.2.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# ... and more&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The UX problem is obvious.&#10;If I want to transcribe voice to text, do I need &lt;code&gt;audio&lt;/code&gt;? &lt;code&gt;llm&lt;/code&gt;? &lt;code&gt;vad&lt;/code&gt;? All three?&#10;What if I want the Whisper server to run locally—is that &lt;code&gt;faster-whisper&lt;/code&gt; or &lt;code&gt;mlx-whisper&lt;/code&gt;?&#10;Do I also need &lt;code&gt;server&lt;/code&gt; for that?&lt;/p&gt;&#10;&lt;p&gt;Expecting users to study a dependency matrix before running a CLI command is a terrible experience.&#10;I wanted three things:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;A lightweight base install that&amp;rsquo;s fast and doesn&amp;rsquo;t pull in heavy ML dependencies.&lt;/li&gt;&#10;&lt;li&gt;Commands that &amp;ldquo;just work&amp;rdquo; when first used.&lt;/li&gt;&#10;&lt;li&gt;A clear opt-out for people who don&amp;rsquo;t want automatic installs.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="3-the-decorator"&gt;3. The decorator &lt;a class="anchor" href="#3-the-decorator" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;The core idea is simple.&#10;Each CLI command declares what it needs, and the decorator handles the rest:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@app.command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;transcribe&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rich_help_panel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Voice Commands&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@requires_extras&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;audio&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;llm&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transcribe&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;...&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s it from the command author&amp;rsquo;s perspective.&#10;One line, and you never think about dependency management in that command again.&lt;/p&gt;&#10;&lt;p&gt;Here are a few more examples from the codebase to show how this scales:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Continuous transcription needs audio, voice activity detection, and an LLM&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@requires_extras&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;audio&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;vad&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;llm&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transcribe_live&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;...&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# TTS server needs the server framework + one of two TTS backends + Wyoming protocol&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@requires_extras&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;server&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;piper|kokoro&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;wyoming&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_tts_server&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;...&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Whisper server: any of three ASR backends will do&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@requires_extras&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;server&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;faster-whisper|mlx-whisper|whisper-transformers&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;wyoming&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_whisper_server&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;...&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Notice the pipe syntax: &lt;code&gt;&amp;quot;piper|kokoro&amp;quot;&lt;/code&gt; means &amp;ldquo;you need &lt;em&gt;either&lt;/em&gt; Piper &lt;em&gt;or&lt;/em&gt; Kokoro—any one of these satisfies the requirement.&amp;rdquo;&#10;On Apple Silicon, if you already have &lt;code&gt;mlx-whisper&lt;/code&gt; installed, it won&amp;rsquo;t try to install &lt;code&gt;faster-whisper&lt;/code&gt; instead.&lt;/p&gt;&#10;&lt;p&gt;The &lt;a href="https://github.com/basnijholt/agent-cli/blob/c9a67b484331b64733edba8d72d20523699c5a72/agent_cli/core/deps.py#L365-L383" target="_blank" rel="noopener"&gt;decorator itself&lt;/a&gt; is straightforward:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;requires_extras&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;extras&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&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;F&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decorator&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;F&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;F&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nd"&gt;@functools.wraps&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_check_and_install_extras&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;extras&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;typer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;decorator&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The real complexity lives in &lt;a href="https://github.com/basnijholt/agent-cli/blob/c9a67b484331b64733edba8d72d20523699c5a72/agent_cli/core/deps.py#L321-L359" target="_blank" rel="noopener"&gt;&lt;code&gt;_check_and_install_extras&lt;/code&gt;&lt;/a&gt;, which handles several installation scenarios.&#10;But the interface for command authors is always a single line.&lt;/p&gt;&#10;&lt;h2 id="4-checking-without-importing"&gt;4. Checking without importing &lt;a class="anchor" href="#4-checking-without-importing" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;You don&amp;rsquo;t want to &lt;em&gt;import&lt;/em&gt; &lt;code&gt;torch&lt;/code&gt; just to check if it&amp;rsquo;s available—that takes seconds and loads hundreds of megabytes into memory.&#10;Instead, I use &lt;code&gt;importlib.util.find_spec&lt;/code&gt;, which checks whether a module exists on the import path without executing any of its code.&#10;No heavy imports, no GPU initialization, no side effects.&#10;The check is essentially free.&lt;/p&gt;&#10;&lt;h2 id="5-the-metadata-bridge"&gt;5. The metadata bridge &lt;a class="anchor" href="#5-the-metadata-bridge" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;There&amp;rsquo;s a gap between what &lt;code&gt;pyproject.toml&lt;/code&gt; knows and what the runtime check needs.&#10;&lt;code&gt;pyproject.toml&lt;/code&gt; says the &lt;code&gt;rag&lt;/code&gt; extra requires &lt;code&gt;chromadb&amp;gt;=0.4.22&lt;/code&gt;—that&amp;rsquo;s the &lt;em&gt;pip package name&lt;/em&gt;.&#10;But at runtime, I need to check &lt;em&gt;Python import names&lt;/em&gt;.&#10;Sometimes these are the same (&lt;code&gt;chromadb&lt;/code&gt; → &lt;code&gt;import chromadb&lt;/code&gt;), but often they&amp;rsquo;re not: &lt;code&gt;pydantic-ai-slim&lt;/code&gt; is imported as &lt;code&gt;pydantic_ai&lt;/code&gt;, &lt;code&gt;piper-tts&lt;/code&gt; as &lt;code&gt;piper&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;To bridge this, I generate an &lt;a href="https://github.com/basnijholt/agent-cli/blob/c9a67b484331b64733edba8d72d20523699c5a72/agent_cli/_extras.json" target="_blank" rel="noopener"&gt;&lt;code&gt;_extras.json&lt;/code&gt;&lt;/a&gt; file that maps each extra to its description and the Python import names needed for the runtime check:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;audio&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Audio recording/playback&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;sounddevice&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;rag&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;RAG proxy (ChromaDB, embeddings)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;chromadb&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pydantic_ai&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;memory&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Long-term memory proxy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;chromadb&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;yaml&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pydantic_ai&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;vad&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Voice Activity Detection (Silero VAD via ONNX)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;onnxruntime&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;faster-whisper&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Whisper ASR via CTranslate2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;faster_whisper&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This file is &lt;a href="https://github.com/basnijholt/agent-cli/blob/c9a67b484331b64733edba8d72d20523699c5a72/.github/scripts/sync_extras.py" target="_blank" rel="noopener"&gt;generated by a script&lt;/a&gt; that parses &lt;code&gt;pyproject.toml&lt;/code&gt; and applies a mapping table for the package-name-to-import-name conversions.&#10;A pre-commit hook ensures it stays in sync—if you add an extra to &lt;code&gt;pyproject.toml&lt;/code&gt; and forget to update the metadata, the commit fails.&lt;/p&gt;&#10;&lt;p&gt;I wish Python packaging had a standard way to discover a package&amp;rsquo;s top-level import name from its distribution name.&#10;It doesn&amp;rsquo;t, so the mapping table is the least-bad option I found.&lt;/p&gt;&#10;&lt;h2 id="6-three-environments-three-strategies"&gt;6. Three environments, three strategies &lt;a class="anchor" href="#6-three-environments-three-strategies" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;The hard part is that people run &lt;code&gt;agent-cli&lt;/code&gt; in several different ways, and each needs a different installation strategy.&lt;/p&gt;&#10;&lt;h3 id="a-uv-tool-install--the-persistent-environment"&gt;A) &lt;code&gt;uv tool install&lt;/code&gt; — the persistent environment &lt;a class="anchor" href="#a-uv-tool-install--the-persistent-environment" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;When installed via &lt;code&gt;uv tool install agent-cli&lt;/code&gt;, the tool lives in a persistent environment under &lt;code&gt;~/.local/share/uv/tools/agent-cli/&lt;/code&gt;.&#10;This environment has a &lt;code&gt;uv-receipt.toml&lt;/code&gt; file that tracks which extras are installed.&lt;/p&gt;&#10;&lt;p&gt;When auto-install triggers, the system reads the current extras from that receipt, merges in the new ones, and reinstalls:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;install_extras_impl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;extras&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;quiet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_uv_tool_install&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;current_extras&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_get_current_uv_tool_extras&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;new_extras&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_extras&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;extras&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_install_via_uv_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_extras&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quiet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;quiet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So if you already had &lt;code&gt;audio&lt;/code&gt; installed and now run a command that needs &lt;code&gt;llm&lt;/code&gt;, it becomes &lt;code&gt;uv tool install agent-cli[audio,llm] --force&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;The key insight: &lt;code&gt;uv tool upgrade&lt;/code&gt; reads &lt;code&gt;uv-receipt.toml&lt;/code&gt; and preserves the extras list.&#10;Once auto-install adds an extra, it persists across future upgrades.&#10;You never have to install it again.&lt;/p&gt;&#10;&lt;p&gt;(&lt;a href="https://github.com/basnijholt/agent-cli/blob/c9a67b484331b64733edba8d72d20523699c5a72/agent_cli/core/deps.py#L179-L235" target="_blank" rel="noopener"&gt;Full implementation&lt;/a&gt;)&lt;/p&gt;&#10;&lt;h3 id="b-uvx--the-ephemeral-environment"&gt;B) &lt;code&gt;uvx&lt;/code&gt; — the ephemeral environment &lt;a class="anchor" href="#b-uvx--the-ephemeral-environment" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;&lt;code&gt;uvx&lt;/code&gt; is uv&amp;rsquo;s tool for running packages in temporary, disposable environments.&#10;Running &lt;code&gt;uvx agent-cli transcribe&lt;/code&gt; creates a fresh environment, installs &lt;code&gt;agent-cli&lt;/code&gt;, runs the command, and throws the environment away.&lt;/p&gt;&#10;&lt;p&gt;The problem: there&amp;rsquo;s nowhere to persist extras.&#10;No &lt;code&gt;uv-receipt.toml&lt;/code&gt; to update, no virtualenv that survives the invocation.&lt;/p&gt;&#10;&lt;p&gt;The solution is one of my favorite tricks in this system: re-execute the entire command with the extras baked into the &lt;code&gt;uvx&lt;/code&gt; invocation itself:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_maybe_reexec_with_uvx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;extras&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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;_REEXEC_MARKER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;_is_uvx_cache&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;uvx_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;which&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;uvx&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;uvx_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;extras_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;,&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;extras&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;uvx_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;--python&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;3.13&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;agent-cli[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;extras_str&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_maybe_exec_with_marker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&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;Re-running with extras: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;extras_str&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So &lt;code&gt;uvx agent-cli transcribe&lt;/code&gt; transparently becomes &lt;code&gt;uvx agent-cli[audio,llm] transcribe&lt;/code&gt;.&#10;The user sees a brief &amp;ldquo;Re-running with extras: audio, llm&amp;rdquo; message, and then the command works.&lt;/p&gt;&#10;&lt;p&gt;Environment detection is straightforward—&lt;code&gt;uvx&lt;/code&gt; environments live under the uv cache directory:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_is_uvx_cache&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;prefix_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_posix&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/cache/uv/&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;prefix_str&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/archive-v&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;prefix_str&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;(&lt;a href="https://github.com/basnijholt/agent-cli/blob/c9a67b484331b64733edba8d72d20523699c5a72/agent_cli/core/deps.py#L287-L301" target="_blank" rel="noopener"&gt;Full implementation&lt;/a&gt;)&lt;/p&gt;&#10;&lt;h3 id="c-regular-virtualenv-pipx-or-system-install"&gt;C) Regular virtualenv, &lt;code&gt;pipx&lt;/code&gt;, or system install &lt;a class="anchor" href="#c-regular-virtualenv-pipx-or-system-install" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;For plain &lt;code&gt;pip install agent-cli&lt;/code&gt;, &lt;code&gt;pipx&lt;/code&gt;, or virtualenv installs, the system falls back to direct installation.&#10;Each extra has a pre-generated requirements file with pinned versions (e.g., &lt;code&gt;_requirements/audio.txt&lt;/code&gt;), and the install command adapts to whatever&amp;rsquo;s available:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_install_cmd&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;in_venv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_prefix&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;which&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;uv&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cmd&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;uv&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pip&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;install&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;--python&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;executable&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;in_venv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cmd&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="s2"&gt;&amp;#34;--system&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;executable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-m&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pip&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;install&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;in_venv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cmd&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="s2"&gt;&amp;#34;--user&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Prefers &lt;code&gt;uv pip&lt;/code&gt; when available (fast), falls back to regular pip.&#10;Uses &lt;code&gt;--system&lt;/code&gt; or &lt;code&gt;--user&lt;/code&gt; as appropriate when not in a virtualenv.&#10;This is also the path that &lt;code&gt;pipx&lt;/code&gt; installations take—&lt;code&gt;pipx&lt;/code&gt; creates a regular virtualenv under the hood, so the auto-installer detects it as a venv and installs directly into it.&lt;/p&gt;&#10;&lt;p&gt;(&lt;a href="https://github.com/basnijholt/agent-cli/blob/c9a67b484331b64733edba8d72d20523699c5a72/agent_cli/core/deps.py#L215-L248" target="_blank" rel="noopener"&gt;Full implementation&lt;/a&gt;)&lt;/p&gt;&#10;&lt;h2 id="7-preventing-infinite-re-execution-loops"&gt;7. Preventing infinite re-execution loops &lt;a class="anchor" href="#7-preventing-infinite-re-execution-loops" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;There&amp;rsquo;s an obvious footgun here: what if the install succeeds but the package still isn&amp;rsquo;t importable for some reason?&#10;You&amp;rsquo;d get an infinite loop of install → re-exec → check → install → re-exec → &amp;hellip;&lt;/p&gt;&#10;&lt;p&gt;The fix is a simple environment variable marker:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;_REEXEC_MARKER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;_AGENT_CLI_REEXEC&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_maybe_exec_with_marker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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;_REEXEC_MARKER&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="c1"&gt;# Already re-executed once, don&amp;#39;t loop&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;new_env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;new_env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_REEXEC_MARKER&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execvpe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;os.execvpe&lt;/code&gt; replaces the current process entirely—it never returns.&#10;The marker ensures we only do this once.&#10;If the second run still can&amp;rsquo;t find the packages, it falls back to a helpful error message instead of looping forever.&lt;/p&gt;&#10;&lt;h2 id="8-what-users-actually-see"&gt;8. What users actually see &lt;a class="anchor" href="#8-what-users-actually-see" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;With auto-install &lt;strong&gt;enabled&lt;/strong&gt; (the default), a first run looks like this:&lt;/p&gt;&#10;&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;Auto-installing missing extras: audio, llm&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Running: uv tool install agent-cli[audio,llm] --force --python 3.13 -q&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Installation complete!&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Re-running with installed extras...&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And then the command runs normally.&#10;Next time, it runs instantly—no install needed.&lt;/p&gt;&#10;&lt;p&gt;With auto-install &lt;strong&gt;disabled&lt;/strong&gt;, the user gets a clear message telling them exactly what to do:&lt;/p&gt;&#10;&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;This command requires the &amp;#39;audio&amp;#39; extra (Audio recording/playback).&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Install with:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; uv tool install -p 3.13 &amp;#34;agent-cli\[audio]&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; # or&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; agent-cli install-extras audio&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you prefer explicit control, there&amp;rsquo;s also a manual &lt;a href="https://github.com/basnijholt/agent-cli/blob/c9a67b484331b64733edba8d72d20523699c5a72/agent_cli/install/extras.py#L24-L104" target="_blank" rel="noopener"&gt;&lt;code&gt;install-extras&lt;/code&gt;&lt;/a&gt; command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;agent-cli install-extras rag memory &lt;span class="c1"&gt;# Install specific extras&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;agent-cli install-extras --all &lt;span class="c1"&gt;# Install everything&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;agent-cli install-extras --list &lt;span class="c1"&gt;# Show available extras with descriptions&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can opt out via environment variable or config file:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;AGENT_CLI_NO_AUTO_INSTALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# ~/.config/agent-cli/config.toml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;auto_install_extras&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="9-keeping-everything-in-sync"&gt;9. Keeping everything in sync &lt;a class="anchor" href="#9-keeping-everything-in-sync" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;The runtime system depends on two generated artifacts:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;&lt;code&gt;_extras.json&lt;/code&gt;&lt;/strong&gt;: maps extras to descriptions and import names (for the &lt;code&gt;find_spec&lt;/code&gt; check)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&lt;code&gt;_requirements/*.txt&lt;/code&gt;&lt;/strong&gt;: pinned dependency files for each extra (for the pip/uv install)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;code&gt;_extras.json&lt;/code&gt; is generated by &lt;a href="https://github.com/basnijholt/agent-cli/blob/c9a67b484331b64733edba8d72d20523699c5a72/.github/scripts/sync_extras.py" target="_blank" rel="noopener"&gt;&lt;code&gt;sync_extras.py&lt;/code&gt;&lt;/a&gt; (run manually or in CI).&#10;The requirements files are generated by &lt;a href="https://github.com/basnijholt/agent-cli/blob/c9a67b484331b64733edba8d72d20523699c5a72/.github/scripts/sync_requirements.py" target="_blank" rel="noopener"&gt;&lt;code&gt;sync_requirements.py&lt;/code&gt;&lt;/a&gt;, which runs as a pre-commit hook whenever &lt;code&gt;pyproject.toml&lt;/code&gt; or &lt;code&gt;uv.lock&lt;/code&gt; changes.&#10;A &lt;a href="https://github.com/basnijholt/agent-cli/blob/c9a67b484331b64733edba8d72d20523699c5a72/.github/scripts/check_extras_sync.py" target="_blank" rel="noopener"&gt;check script&lt;/a&gt; enforces that &lt;code&gt;_extras.json&lt;/code&gt; stays consistent with &lt;code&gt;pyproject.toml&lt;/code&gt;—if you add an extra but forget to update the metadata, the commit fails with a clear error.&lt;/p&gt;&#10;&lt;p&gt;This means the runtime system never drifts from the source of truth.&#10;&lt;code&gt;pyproject.toml&lt;/code&gt; is the canonical definition, everything else is derived.&lt;/p&gt;&#10;&lt;h2 id="10-why-uv-made-this-practical"&gt;10. Why uv made this practical &lt;a class="anchor" href="#10-why-uv-made-this-practical" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;I want to be clear: this system would be &lt;em&gt;much&lt;/em&gt; harder without &lt;a href="https://docs.astral.sh/uv/" target="_blank" rel="noopener"&gt;uv&lt;/a&gt;.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;&lt;code&gt;uv tool install&lt;/code&gt;&lt;/strong&gt; with persistent extras in &lt;code&gt;uv-receipt.toml&lt;/code&gt;—extras survive upgrades automatically&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&lt;code&gt;uvx&lt;/code&gt;&lt;/strong&gt; for ephemeral execution with extras specified inline&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&lt;code&gt;uv pip install&lt;/code&gt;&lt;/strong&gt; that&amp;rsquo;s fast enough for runtime auto-install to feel acceptable&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&lt;code&gt;uv export&lt;/code&gt;&lt;/strong&gt; for generating pinned requirements files from &lt;code&gt;uv.lock&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;With traditional pip, auto-installing at runtime would take 30-60 seconds and feel terrible.&#10;With uv, it typically takes a second.&#10;That&amp;rsquo;s the difference between &amp;ldquo;annoying&amp;rdquo; and &amp;ldquo;barely noticeable.&amp;rdquo;&lt;/p&gt;&#10;&lt;h2 id="11-tradeoffs"&gt;11. Tradeoffs &lt;a class="anchor" href="#11-tradeoffs" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;This approach works well, but it&amp;rsquo;s still a tradeoff:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;There&amp;rsquo;s metadata to maintain.&lt;/strong&gt; The package-name-to-import-name mapping requires a lookup table with special cases like &lt;code&gt;piper-tts&lt;/code&gt; → &lt;code&gt;piper&lt;/code&gt; and &lt;code&gt;pydantic-ai-slim&lt;/code&gt; → &lt;code&gt;pydantic_ai&lt;/code&gt;. I wish &lt;code&gt;pyproject.toml&lt;/code&gt; had a field for this.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;The check only verifies importability, not version compatibility.&lt;/strong&gt; &lt;code&gt;find_spec&lt;/code&gt; tells you a module exists, not whether it&amp;rsquo;s the right version. In practice this hasn&amp;rsquo;t been a problem because uv&amp;rsquo;s pinned requirements handle versions, but it&amp;rsquo;s a theoretical gap.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;Auto-install can fail in restricted environments.&lt;/strong&gt; Corporate machines, read-only containers, air-gapped systems—the opt-out exists for a reason.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;Re-exec is a CLI pattern, not a library pattern.&lt;/strong&gt; &lt;code&gt;os.execvpe&lt;/code&gt; replaces the process, which means any shell hooks or signal handlers set up before the decorator runs are lost. Fine for CLI tools, would be problematic for libraries.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="12-not-dependency-injection"&gt;12. Not dependency injection &lt;a class="anchor" href="#12-not-dependency-injection" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;I originally called this &amp;ldquo;dependency injection&amp;rdquo; in my head, but that&amp;rsquo;s the wrong term entirely.&#10;Dependency injection is about providing object dependencies through constructors or function parameters.&#10;This is something different.&lt;/p&gt;&#10;&lt;p&gt;The best names I&amp;rsquo;ve come up with:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;runtime optional dependency resolution&lt;/strong&gt; — accurate but a mouthful&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;on-demand extras installation&lt;/strong&gt; — emphasizes the user experience&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;lazy extras&lt;/strong&gt; — short, probably what I&amp;rsquo;d use in conversation&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;If there&amp;rsquo;s a standard term for this pattern, I haven&amp;rsquo;t found it.&lt;/p&gt;&#10;&lt;h2 id="13-conclusion"&gt;13. Conclusion &lt;a class="anchor" href="#13-conclusion" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;The underlying principle is simple: &lt;strong&gt;the tool should know what it needs, and it should get it&lt;/strong&gt;.&#10;Users shouldn&amp;rsquo;t have to study a matrix of 13 optional extras to figure out which pip command to run.&#10;They should just type the command they want, and it should work.&lt;/p&gt;&#10;&lt;p&gt;uv&amp;rsquo;s speed and tooling model made this practical.&#10;A decorator made it ergonomic for command authors.&#10;Pre-commit hooks made it maintainable.&#10;And three environment-detection strategies made it work regardless of how the user installed the tool.&lt;/p&gt;&#10;&lt;p&gt;If you&amp;rsquo;re building a Python CLI with optional features that keep growing, consider this pattern.&#10;Your users will thank you, mostly by never having to ask &amp;ldquo;which extras do I need?&amp;rdquo;&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;em&gt;The code discussed here is part of &lt;a href="https://github.com/basnijholt/agent-cli" target="_blank" rel="noopener"&gt;&lt;code&gt;agent-cli&lt;/code&gt;&lt;/a&gt;, an open-source suite of AI-powered command-line tools.&#10;The implementation lives primarily in &lt;a href="https://github.com/basnijholt/agent-cli/blob/c9a67b484331b64733edba8d72d20523699c5a72/agent_cli/core/deps.py" target="_blank" rel="noopener"&gt;&lt;code&gt;agent_cli/core/deps.py&lt;/code&gt;&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;&#10;</description></item><item><title>Confessions of an asymmetric hypocrite: on Python dependencies</title><link>https://www.nijho.lt/post/dependencies/</link><pubDate>Sun, 27 Apr 2025 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/dependencies/</guid><description>&lt;h2 id="introduction-the-dependency-dilemma-"&gt;Introduction: The Dependency Dilemma 🤔 &lt;a class="anchor" href="#introduction-the-dependency-dilemma-" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;I just listened to a podcast &amp;ldquo;&lt;a href="https://www.youtube.com/watch?v=5ZyzeeYZgeM" target="_blank" rel="noopener"&gt;Build software that lasts!&lt;/a&gt;&amp;rdquo; with Bert Hubert (a fellow Dutch), who prefers to reduce the number of dependencies to an absolute minimum (also see his related blog posts [&lt;a href="https://berthub.eu/articles/posts/on-long-term-software-development/" target="_blank" rel="noopener"&gt;1&lt;/a&gt;] and [&lt;a href="https://berthub.eu/articles/posts/a-2024-plea-for-lean-software/" target="_blank" rel="noopener"&gt;2&lt;/a&gt;]).&#10;While I agree one shouldn&amp;rsquo;t use &lt;a href="https://www.npmjs.com/package/is-even" target="_blank" rel="noopener"&gt;&lt;code&gt;is-even&lt;/code&gt;&lt;/a&gt; as a dependency (&lt;a href="https://www.npmjs.com/package/is-odd" target="_blank" rel="noopener"&gt;&lt;code&gt;is-odd&lt;/code&gt;&lt;/a&gt; is a better choice 😏), I think there is a lot of value in reducing the number of dependencies but &lt;em&gt;also&lt;/em&gt; in using the right tool for the job and not continuously re-inventing the wheel.&#10;The podcast got me thinking about my own stance on dependencies and how I actively avoid introducing dependencies into my libraries while expecting (hoping 🤞) others to depend on my work.&lt;/p&gt;&#10;&lt;p&gt;Like many software developers, I&amp;rsquo;ve spent countless hours wrestling with dependencies.&#10;Dependency hell is easy to fall into, &lt;em&gt;especially&lt;/em&gt; in dynamic languages like Python.&#10;Those moments where installing or updating one package breaks another, or where a tiny library pulls in hundreds of transitive dependencies, are frustratingly common.&#10;This frustration partly motivated me to create &lt;a href="https://github.com/basnijholt/unidep" target="_blank" rel="noopener"&gt;&lt;code&gt;unidep&lt;/code&gt;&lt;/a&gt;, a tool aimed at simplifying dependency management across different Python tools like &lt;code&gt;pip&lt;/code&gt; and &lt;code&gt;conda&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;But working on &lt;code&gt;unidep&lt;/code&gt; and my other projects forces me to think about my own relationship with dependencies.&#10;I&amp;rsquo;ve realized I operate with what feels like an &amp;ldquo;asymmetric hypocrisy.&amp;rdquo;&#10;My standards for the libraries I &lt;em&gt;publish&lt;/em&gt; are extremely strict regarding which external dependencies I &lt;em&gt;consume&lt;/em&gt;.&#10;Yet, as an author of over 30 Python packages, I inherently &lt;em&gt;produce&lt;/em&gt; dependencies, hoping others will adopt my work.&#10;And then, when building applications or CLIs, my caution often disappears entirely.&lt;/p&gt;&#10;&lt;p&gt;This post dives into that asymmetry.&#10;It&amp;rsquo;s about the different hats I wear: the minimalist library author (as a dependency &lt;em&gt;consumer&lt;/em&gt;), the hopeful library author (as a dependency &lt;em&gt;producer&lt;/em&gt;), and the pragmatic application developer.&#10;In this post, I&amp;rsquo;ll explain why I take these different approaches and why it&amp;rsquo;s really about the situation, not a contradiction.&lt;/p&gt;&#10;&lt;details class="toc" open&gt;&lt;summary&gt;Table of contents&lt;/summary&gt;&lt;nav id="TableOfContents"&gt;&#10; &lt;ul&gt;&#10; &lt;li&gt;&lt;a href="#introduction-the-dependency-dilemma-"&gt;Introduction: The Dependency Dilemma 🤔&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#1-the-library-author-hat--as-dependency-consumer-maximum-caution"&gt;1. The Library Author Hat 🎩 (as Dependency Consumer): Maximum Caution&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#2-the-library-author-hat--as-dependency-producer-the-paradox"&gt;2. The Library Author Hat 🎩 (as Dependency Producer): The Paradox&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#3-the-application-developer--contributor-hat--practicality-is-key"&gt;3. The Application Developer / Contributor Hat 🧢: Practicality is Key&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#4-conclusion-not-hypocrisy-but-context-driven-strategy-"&gt;4. Conclusion: Not Hypocrisy, but Context-Driven Strategy 🙏&lt;/a&gt;&lt;/li&gt;&#10; &lt;/ul&gt;&#10;&lt;/nav&gt;&lt;/details&gt;&#10;&#10;&lt;aside class="callout callout-note"&gt;&lt;svg class="ico" viewBox="0 0 256 256" aria-hidden="true"&gt;&lt;path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"/&gt;&lt;/svg&gt;&lt;div&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I&amp;rsquo;m extremely strict about adding external dependencies to my &lt;em&gt;libraries&lt;/em&gt;, preferring widely-used, low-dependency packages.&#10;Simultaneously, I &lt;em&gt;create&lt;/em&gt; libraries expecting adoption.&#10;For &lt;em&gt;applications&lt;/em&gt; (especially CLIs in isolated environments), I&amp;rsquo;m far more liberal with dependencies to build features quickly.&#10;It&amp;rsquo;s all about managing risks depending on the situation.&lt;/div&gt;&lt;/aside&gt;&#10;&lt;h2 id="1-the-library-author-hat--as-dependency-consumer-maximum-caution"&gt;1. The Library Author Hat 🎩 (as Dependency Consumer): Maximum Caution &lt;a class="anchor" href="#1-the-library-author-hat--as-dependency-consumer-maximum-caution" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;When I build and publish an open-source Python library – like &lt;a href="https://github.com/python-adaptive/adaptive" target="_blank" rel="noopener"&gt;&lt;code&gt;Adaptive&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/pipefunc/pipefunc" target="_blank" rel="noopener"&gt;&lt;code&gt;pipefunc&lt;/code&gt;&lt;/a&gt;, or &lt;a href="https://github.com/basnijholt/unidep" target="_blank" rel="noopener"&gt;&lt;code&gt;unidep&lt;/code&gt;&lt;/a&gt; – my primary focus shifts to the &lt;em&gt;users&lt;/em&gt; who will depend on my code.&#10;My choices directly impact their projects.&#10;This responsibility means I have to be very careful about adding external dependencies:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Minimize and Scrutinize:&lt;/strong&gt; Fewer dependencies mean fewer potential conflicts for users.&#10;I aggressively minimize the core runtime dependency list.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Favor Battle-Tested Dependencies:&lt;/strong&gt; I strongly prefer dependencies that are already widely adopted and trusted by the community.&#10;(I have been called that person that always wants the latest and greatest, the truth is that I am a sucker for shiny new things but typically only adopt them once they are already quite popular.)&#10;Think &lt;code&gt;numpy&lt;/code&gt; or &lt;code&gt;cloudpickle&lt;/code&gt;.&#10;These are less likely to cause unexpected issues or disappear overnight.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Low Transitive Dependency Count:&lt;/strong&gt; A key criterion is how many &lt;em&gt;other&lt;/em&gt; packages a potential dependency pulls in.&#10;Ideally, I choose dependencies with zero or very few further dependencies.&#10;For example, in &lt;a href="https://github.com/pipefunc/pipefunc" target="_blank" rel="noopener"&gt;&lt;code&gt;pipefunc&lt;/code&gt;&lt;/a&gt;, dependencies like &lt;a href="https://numpy.org/" target="_blank" rel="noopener"&gt;&lt;code&gt;numpy&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://networkx.org/" target="_blank" rel="noopener"&gt;&lt;code&gt;networkx&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://github.com/cloudpipe/cloudpickle" target="_blank" rel="noopener"&gt;&lt;code&gt;cloudpickle&lt;/code&gt;&lt;/a&gt; are good examples – they are popular and don&amp;rsquo;t rely on many other things.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Optional Dependencies for Heavy Features:&lt;/strong&gt; If a feature really needs a large or less common dependency, I try making it a &lt;em&gt;optional&lt;/em&gt; extra (&lt;code&gt;project.optional-dependencies&lt;/code&gt;).&#10;Users opt-in only if they need that specific capability.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Avoid Pinning (Mostly):&lt;/strong&gt; As mentioned before, hard version pins (&lt;code&gt;==1.2.3&lt;/code&gt;) in libraries are usually harmful.&#10;Minimum versions (&lt;code&gt;&amp;gt;=1.2&lt;/code&gt;) or safe upper bounds (&lt;code&gt;&amp;lt;2.0&lt;/code&gt;) are preferred.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;License Awareness:&lt;/strong&gt; The license of a dependency matters immensely, especially regarding compatibility and obligations (&lt;a href="https://www.nijho.lt/post/gpl/"&gt;GPL considerations&lt;/a&gt;).&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;The guiding principle is: &lt;strong&gt;Do not cause pain for your users.&lt;/strong&gt;&#10;A library should integrate smoothly into diverse environments.&lt;/p&gt;&#10;&lt;h2 id="2-the-library-author-hat--as-dependency-producer-the-paradox"&gt;2. The Library Author Hat 🎩 (as Dependency Producer): The Paradox &lt;a class="anchor" href="#2-the-library-author-hat--as-dependency-producer-the-paradox" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Here&amp;rsquo;s where the &amp;ldquo;hypocrisy&amp;rdquo; might seem to surface.&#10;While I&amp;rsquo;m cautious about &lt;em&gt;consuming&lt;/em&gt; dependencies for my libraries, I am also the author of over 30 packages.&#10;I invest time and effort into these tools, naturally hoping they prove useful and that others &lt;em&gt;will adopt them&lt;/em&gt; – making &lt;em&gt;my&lt;/em&gt; libraries dependencies in &lt;em&gt;their&lt;/em&gt; projects.&lt;/p&gt;&#10;&lt;p&gt;Is it hypocritical to be wary of adding dependencies while simultaneously creating them for others?&#10;Perhaps slightly, but it&amp;rsquo;s also the nature of contributing to an ecosystem.&#10;I try to handle this by strictly following the rules from Section 1 for my own libraries.&#10;If I expect others to rely on my code, I feel responsible to make that dependency as&#10;low-risk and stable as possible.&#10;I want my libraries to be the kind of well-behaved, minimal-friction dependencies&#10;that I myself would be comfortable adding.&lt;/p&gt;&#10;&lt;h2 id="3-the-application-developer--contributor-hat--practicality-is-key"&gt;3. The Application Developer / Contributor Hat 🧢: Practicality is Key &lt;a class="anchor" href="#3-the-application-developer--contributor-hat--practicality-is-key" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;When the context shifts to building an application – like a command-line tool, a web service, or a personal project – my rules become &lt;em&gt;much&lt;/em&gt; less strict.&#10;The key differences are isolation (keeping things separate) and how far the effects reach.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Isolation Reduces Risk:&lt;/strong&gt; Applications often run in controlled, isolated environments.&#10;&lt;a href="https://www.docker.com/" target="_blank" rel="noopener"&gt;&lt;code&gt;Docker&lt;/code&gt;&lt;/a&gt;, &lt;code&gt;conda&lt;/code&gt;/&lt;a href="https://mamba.readthedocs.io/en/latest/user_guide/micromamba.html" target="_blank" rel="noopener"&gt;&lt;code&gt;micromamba&lt;/code&gt;&lt;/a&gt; (&lt;a href="https://www.nijho.lt/post/python-environments/"&gt;Python Environment Jungle post&lt;/a&gt;), or simple &lt;code&gt;venv&lt;/code&gt;s contain the dependencies.&#10;A conflict within the application&amp;rsquo;s environment doesn&amp;rsquo;t break the user&amp;rsquo;s entire system or other unrelated projects.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;CLIs as a Prime Example:&lt;/strong&gt; Many tools I&amp;rsquo;ve built are CLIs (like &lt;a href="https://github.com/basnijholt/tuitorial" target="_blank" rel="noopener"&gt;&lt;code&gt;tuitorial&lt;/code&gt;&lt;/a&gt; or helpers within my &lt;a href="https://github.com/basnijholt/dotfiles" target="_blank" rel="noopener"&gt;&lt;code&gt;dotfiles&lt;/code&gt;&lt;/a&gt;).&#10;These might have tens of dependencies.&#10;Because they are installed into isolated environments (often via &lt;a href="https://docs.astral.sh/uv/" target="_blank" rel="noopener"&gt;&lt;code&gt;uv&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://pipx.pypa.io/stable/" target="_blank" rel="noopener"&gt;&lt;code&gt;pipx&lt;/code&gt;&lt;/a&gt;, or within a dedicated &lt;code&gt;conda&lt;/code&gt; env), this complexity is okay.&#10;The risk is contained.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Developer Tools Welcome:&lt;/strong&gt; Productivity boosters like &lt;a href="https://beta.ruff.rs/docs/rules/" target="_blank" rel="noopener"&gt;&lt;code&gt;ruff&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://mypy.readthedocs.io/en/stable/" target="_blank" rel="noopener"&gt;&lt;code&gt;mypy&lt;/code&gt;&lt;/a&gt;, etc., are fair game as development dependencies (&lt;a href="https://www.nijho.lt/post/best-python-dev-tooling/"&gt;My Python Project Toolkit&lt;/a&gt;).&#10;They don&amp;rsquo;t impact the end-user runtime (if packaged properly), so I am much more lenient with dev tools.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Focus on Functionality:&lt;/strong&gt; The goal is often to deliver features or solve a problem efficiently.&#10;Leveraging existing libraries, even those with dependencies, is usually faster and more robust than reimplementing complex logic.&#10;It usually makes more sense to use existing libraries.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;In this context, the focus changes from &lt;em&gt;minimizing external impact&lt;/em&gt; (libraries) to &lt;em&gt;maximizing internal capability and maintainability&lt;/em&gt; within a controlled scope (applications).&lt;/p&gt;&#10;&lt;h2 id="4-conclusion-not-hypocrisy-but-context-driven-strategy-"&gt;4. Conclusion: Not Hypocrisy, but Context-Driven Strategy 🙏 &lt;a class="anchor" href="#4-conclusion-not-hypocrisy-but-context-driven-strategy-" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;My approach to dependencies &lt;em&gt;is&lt;/em&gt; &amp;ldquo;it depends on the situation&amp;rdquo;.&#10;I&amp;rsquo;m stricter about dependencies for my libraries than for my applications or personal tools.&#10;I am cautious about adopting dependencies while actively producing them for others.&lt;/p&gt;&#10;&lt;p&gt;But I don&amp;rsquo;t see this as hypocrisy.&#10;It&amp;rsquo;s a practical approach based on different situations and goals:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Libraries (as Consumer):&lt;/strong&gt; Focus on keeping the software community stable and making things easy for users. Be conservative.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Libraries (as Producer):&lt;/strong&gt; Create software responsibly, following the same high standards you expect from others.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Applications:&lt;/strong&gt; Focus on adding features and developing quickly within separate environments. Be pragmatic.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Understanding the different risks and goals for each situation helps make good decisions about dependencies.&#10;It&amp;rsquo;s about choosing the right trade-offs for the specific situation, whether you&amp;rsquo;re building a foundational block for the community or a specific tool for a controlled environment.&lt;/p&gt;&#10;&lt;p&gt;How do you handle dependencies across different types of projects?&#10;Do you find yourself navigating similar questions?&#10;Share your thoughts!&lt;/p&gt;&#10;</description></item><item><title>GPL for Python packaging ⚖️</title><link>https://www.nijho.lt/post/gpl/</link><pubDate>Mon, 10 Feb 2025 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/gpl/</guid><description>&lt;h2 id="understanding-gpl-for-python-packaging"&gt;Understanding GPL for Python Packaging &lt;a class="anchor" href="#understanding-gpl-for-python-packaging" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Over the last 10 years–since I started working on and with open source projects–the GPL topic has frequently come up with friends and colleagues.&#10;I simply write this post as a reminder to myself and to have something concise and clear to link to later.&#10;Also, &lt;a href="https://en.wikipedia.org/wiki/IANAL" target="_blank" rel="noopener"&gt;IANAL&lt;/a&gt;, so if you are relying on this post, please do your own research, I have included all the links I used to make this post.&lt;/p&gt;&#10;&lt;p&gt;The GNU General Public License (GPL) is widely used in the open-source world, but its implications can be unclear, especially regarding Python packaging.&#10;If you develop or distribute Python software, understanding GPL variants is crucial.&lt;/p&gt;&#10;&lt;h3 id="what-is-gpl"&gt;What is GPL? &lt;a class="anchor" href="#what-is-gpl" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;GPL is a strong copyleft license—meaning software using GPL-licensed code must also be released under the GPL.&#10;There are several key variants relevant to Python:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;GPLv2 &amp;amp; GPLv3&lt;/strong&gt;:&#10;These licenses require that software using GPL code (directly or indirectly) also be open-sourced under GPL upon distribution.(&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;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;)&#10;If your Python application imports or depends on a GPL package, your code must adopt GPL licensing.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;LGPL (Lesser GPL)&lt;/strong&gt;:&#10;LGPL is more permissive and is usually applied to libraries.&#10;It allows proprietary software to link or import the library without forcing the proprietary software itself to adopt LGPL.(&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;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;)&#10;You just need to ensure users can replace or modify the LGPL component.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;AGPL (Affero GPL)&lt;/strong&gt;:&#10;AGPL is similar to GPLv3 but includes a critical extra clause: it applies even when users access the software remotely over a network (such as web services or APIs).&#10;If you use an AGPL-licensed Python library, your entire application must be open-sourced under AGPL, even if you&amp;rsquo;re only hosting it online.(&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;,&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;)&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="what-does-linking-mean-in-python"&gt;What Does &amp;ldquo;Linking&amp;rdquo; Mean in Python? &lt;a class="anchor" href="#what-does-linking-mean-in-python" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;GPL licenses often refer to &amp;ldquo;linking,&amp;rdquo; traditionally associated with compiled languages (e.g., C, C++).&#10;Python doesn&amp;rsquo;t link code in the traditional sense.&#10;However, it is generally agreed upon that importing a Python module counts as linking for licensing purposes.(&lt;sup id="fnref1:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;,&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;)&#10;Therefore, directly importing a GPL-licensed Python package typically triggers the GPL&amp;rsquo;s copyleft requirements.&lt;/p&gt;&#10;&lt;h3 id="why-does-this-matter-for-python-packaging"&gt;Why Does This Matter for Python Packaging? &lt;a class="anchor" href="#why-does-this-matter-for-python-packaging" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;Python makes it easy to incorporate third-party libraries.&#10;But unintentionally using GPL-licensed libraries (especially GPL or AGPL) legally obligates you to open-source your proprietary code.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;Direct Dependencies&lt;/strong&gt;:&#10;If you directly import a GPL-licensed Python library, your software must be GPL-licensed upon distribution.(&lt;sup id="fnref2:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;,&lt;sup id="fnref1:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;)&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;Indirect (Transitive) Dependencies&lt;/strong&gt;:&#10;If any dependency in your Python project indirectly relies on GPL-licensed code, your entire software must also adopt GPL licensing upon distribution.&#10;Even a hidden, indirect GPL dependency triggers the GPL requirements.(&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;,&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;,&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;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="recommendations"&gt;Recommendations &lt;a class="anchor" href="#recommendations" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;Check Licenses Carefully&lt;/strong&gt;:&#10;Always verify the licenses of both your direct and indirect dependencies to prevent unintended GPL licensing obligations.(&lt;sup id="fnref:11"&gt;&lt;a href="#fn:11" class="footnote-ref" role="doc-noteref"&gt;11&lt;/a&gt;&lt;/sup&gt;,&lt;sup id="fnref1:7"&gt;&lt;a href="#fn:7" class="footnote-ref" role="doc-noteref"&gt;7&lt;/a&gt;&lt;/sup&gt;)&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;Use Permissive or LGPL Licenses&lt;/strong&gt;:&#10;When planning to keep your own software proprietary, prioritize libraries with permissive licenses (e.g., MIT, Apache, BSD) or LGPL-licensed libraries, as they allow for proprietary use without triggering GPL requirements.(&lt;sup id="fnref1:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;,&lt;sup id="fnref1:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;)&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;Separate GPL Code&lt;/strong&gt;:&#10;If you must utilize GPL code, explicitly separate it from proprietary code.&#10;Using subprocess calls or isolated microservices is often recommended since it counts as &amp;ldquo;mere aggregation,&amp;rdquo; not linking, thus avoiding GPL obligations.(&lt;sup id="fnref:12"&gt;&lt;a href="#fn:12" class="footnote-ref" role="doc-noteref"&gt;12&lt;/a&gt;&lt;/sup&gt;,&lt;sup id="fnref2:7"&gt;&lt;a href="#fn:7" class="footnote-ref" role="doc-noteref"&gt;7&lt;/a&gt;&lt;/sup&gt;)&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;By clearly understanding GPL licensing implications, you can confidently manage your Python project&amp;rsquo;s dependencies and safely distribute your software.&lt;/p&gt;&#10;&lt;!-- Footnotes --&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;&lt;a href="https://www.gnu.org/licenses/gpl-faq.html#IfLibraryIsGPL" target="_blank" rel="noopener"&gt;GNU GPL FAQ - If a library is released under the GPL&lt;/a&gt;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref1:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref2:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:2"&gt;&#10;&lt;p&gt;&lt;a href="https://opensource.stackexchange.com/questions/35/if-i-include-some-gpl-code-in-my-project-can-i-release-it-as-bsd" target="_blank" rel="noopener"&gt;Stack Exchange - If I include some GPL code in my project, can I release it as BSD?&lt;/a&gt;&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref1:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:3"&gt;&#10;&lt;p&gt;&lt;a href="https://www.gnu.org/licenses/lgpl-3.0.en.html" target="_blank" rel="noopener"&gt;GNU Lesser General Public License&lt;/a&gt;&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref1:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:4"&gt;&#10;&lt;p&gt;&lt;a href="https://opensource.stackexchange.com/questions/6831/can-i-use-gpl-3-0-or-lgpl-licensed-libraries-in-my-commercial-closed-source-iot" target="_blank" rel="noopener"&gt;Stack Exchange - Can I use GPL/LGPL libraries in commercial closed source projects?&lt;/a&gt;&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref1:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:5"&gt;&#10;&lt;p&gt;&lt;a href="https://www.gnu.org/licenses/agpl-3.0.en.html" target="_blank" rel="noopener"&gt;GNU Affero General Public License&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;&#10;&lt;/li&gt;&#10;&lt;li id="fn:6"&gt;&#10;&lt;p&gt;&lt;a href="https://opensource.stackexchange.com/questions/11826/can-i-use-a-agpl-gpl-package-and-software-for-closed-source-backend-api" target="_blank" rel="noopener"&gt;Stack Exchange - Can I use AGPL/GPL package for closed source backend API?&lt;/a&gt;&amp;#160;&lt;a href="#fnref:6" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:7"&gt;&#10;&lt;p&gt;&lt;a href="https://opensource.stackexchange.com/questions/6033/can-a-non-gpl-python-program-use-gpl-python-module" target="_blank" rel="noopener"&gt;Stack Exchange - Can a non-GPL Python program use a GPL Python module?&lt;/a&gt;&amp;#160;&lt;a href="#fnref:7" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref1:7" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref2:7" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:8"&gt;&#10;&lt;p&gt;&lt;a href="https://www.gnu.org/licenses/gpl-faq.html#AllCompatibility" target="_blank" rel="noopener"&gt;GNU GPL FAQ - License Compatibility&lt;/a&gt;&amp;#160;&lt;a href="#fnref:8" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:9"&gt;&#10;&lt;p&gt;&lt;a href="https://dev.to/wagenrace/8000-python-packages-might-have-to-change-to-gnu-general-public-license-1000" target="_blank" rel="noopener"&gt;Dev.to - 8000 Python packages might have to change to GNU General Public License&lt;/a&gt;&amp;#160;&lt;a href="#fnref:9" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:10"&gt;&#10;&lt;p&gt;&lt;a href="https://stackoverflow.com/questions/6748333/import-a-library-that-imports-a-gpl-library" target="_blank" rel="noopener"&gt;Stack Overflow - Import a library that imports a GPL library&lt;/a&gt;&amp;#160;&lt;a href="#fnref:10" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:11"&gt;&#10;&lt;p&gt;&lt;a href="https://www.gnu.org/licenses/license-list.html" target="_blank" rel="noopener"&gt;GNU License List&lt;/a&gt;&amp;#160;&lt;a href="#fnref:11" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:12"&gt;&#10;&lt;p&gt;&lt;a href="https://www.gnu.org/licenses/gpl-faq.html#MereAggregation" target="_blank" rel="noopener"&gt;GNU GPL FAQ - What is &amp;ldquo;mere aggregation&amp;rdquo;?&lt;/a&gt;&amp;#160;&lt;a href="#fnref:12" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Python environments that stuck: micromamba and uv 🐍</title><link>https://www.nijho.lt/post/python-environments/</link><pubDate>Fri, 03 Jan 2025 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/python-environments/</guid><description>&lt;h2 id="1-introduction-"&gt;1. Introduction 🚀 &lt;a class="anchor" href="#1-introduction-" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Like many Python developers, I&amp;rsquo;ve spent considerable time trying different environment management tools to find what works best for me.&lt;/p&gt;&#10;&lt;p&gt;After exploring various options from virtualenv to Conda, I found that Conda-based solutions fit my needs well - mainly because they handle non-Python dependencies without much hassle.&lt;/p&gt;&#10;&lt;p&gt;While Conda itself can be slow when setting up environments, tools like Mamba and Pixi provide the same functionality with better performance.&lt;/p&gt;&#10;&lt;p&gt;In this post, I&amp;rsquo;ll share my experience with different Python environment tools and provide practical commands for setting up Python environments.&lt;/p&gt;&#10;&lt;aside class="callout callout-note"&gt;&lt;svg class="ico" viewBox="0 0 256 256" aria-hidden="true"&gt;&lt;path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"/&gt;&lt;/svg&gt;&lt;div&gt;TL;DR, I use &lt;code&gt;micromamba&lt;/code&gt; for projects with non-Python dependencies and &lt;code&gt;uv&lt;/code&gt; for projects without them.&#10;This approach works well for me, you own mileage may vary.&lt;/div&gt;&lt;/aside&gt;&#10;&lt;details class="toc" open&gt;&lt;summary&gt;Table of contents&lt;/summary&gt;&lt;nav id="TableOfContents"&gt;&#10; &lt;ul&gt;&#10; &lt;li&gt;&lt;a href="#1-introduction-"&gt;1. Introduction 🚀&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#2-the-alternatives-"&gt;2. The Alternatives 🌟&lt;/a&gt;&#10; &lt;ul&gt;&#10; &lt;li&gt;&lt;a href="#-pyenv"&gt;🐍 Pyenv&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#-pipenv"&gt;📦 Pipenv&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#-conda"&gt;🌐 Conda&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#-mamba"&gt;🚀 Mamba&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#-micromamba"&gt;🌌 Micromamba&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#-docker"&gt;🐳 Docker&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#-venv"&gt;🏞️ Venv&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#-virtualenv"&gt;🌐 Virtualenv&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#-homebrew"&gt;🍺 Homebrew&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#-system-package-managers"&gt;💻 System package managers&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#-pixi"&gt;🌟 Pixi&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#-uv"&gt;⚡ uv&lt;/a&gt;&lt;/li&gt;&#10; &lt;/ul&gt;&#10; &lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#3-conclusion-"&gt;3. Conclusion 🏁&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#4-further-reading-"&gt;4. Further Reading 📚&lt;/a&gt;&lt;/li&gt;&#10; &lt;/ul&gt;&#10;&lt;/nav&gt;&lt;/details&gt;&#10;&#10;&lt;h2 id="2-the-alternatives-"&gt;2. The Alternatives 🌟 &lt;a class="anchor" href="#2-the-alternatives-" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Before we start look at this comparison tables for the most popular Python environment management tools.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;I had to split the tables into two because of the number of columns.&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Table 1: Python Environment Management Tools&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&#10;&lt;div class="table-wrap"&gt;&#10;&lt;table&gt;&#10;&lt;thead&gt;&#10;&lt;tr&gt;&#10;&lt;th&gt;Tool&lt;/th&gt;&#10;&lt;th&gt;Key Features&lt;/th&gt;&#10;&lt;th&gt;Native/3rd Party&lt;/th&gt;&#10;&lt;th&gt;Non-Python Deps&lt;/th&gt;&#10;&lt;/tr&gt;&#10;&lt;/thead&gt;&#10;&lt;tbody&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;a href="https://github.com/pyenv/pyenv" target="_blank" rel="noopener"&gt;Pyenv&lt;/a&gt;&lt;/td&gt;&#10;&lt;td&gt;Python version management&lt;/td&gt;&#10;&lt;td&gt;3rd Party&lt;/td&gt;&#10;&lt;td&gt;❌&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;a href="https://pipenv.pypa.io/en/latest/" target="_blank" rel="noopener"&gt;Pipenv&lt;/a&gt;&lt;/td&gt;&#10;&lt;td&gt;Package &amp;amp; env management&lt;/td&gt;&#10;&lt;td&gt;3rd Party&lt;/td&gt;&#10;&lt;td&gt;❌&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;a href="https://docs.conda.io/en/latest/" target="_blank" rel="noopener"&gt;Conda&lt;/a&gt;&lt;/td&gt;&#10;&lt;td&gt;Package &amp;amp; env management&lt;/td&gt;&#10;&lt;td&gt;3rd Party&lt;/td&gt;&#10;&lt;td&gt;✅&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;a href="https://mamba.readthedocs.io/en/latest/" target="_blank" rel="noopener"&gt;Mamba&lt;/a&gt;&lt;/td&gt;&#10;&lt;td&gt;Fast Conda replacement&lt;/td&gt;&#10;&lt;td&gt;3rd Party&lt;/td&gt;&#10;&lt;td&gt;✅&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;a href="https://mamba.readthedocs.io/en/latest/user_guide/micromamba.html" target="_blank" rel="noopener"&gt;Micromamba&lt;/a&gt;&lt;/td&gt;&#10;&lt;td&gt;Lightweight Mamba&lt;/td&gt;&#10;&lt;td&gt;3rd Party&lt;/td&gt;&#10;&lt;td&gt;✅&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;a href="https://www.docker.com/" target="_blank" rel="noopener"&gt;Docker&lt;/a&gt;&lt;/td&gt;&#10;&lt;td&gt;Containerization&lt;/td&gt;&#10;&lt;td&gt;3rd Party&lt;/td&gt;&#10;&lt;td&gt;✅&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;a href="https://docs.python.org/3/library/venv.html" target="_blank" rel="noopener"&gt;Venv&lt;/a&gt;&lt;/td&gt;&#10;&lt;td&gt;Built-in env management&lt;/td&gt;&#10;&lt;td&gt;Native&lt;/td&gt;&#10;&lt;td&gt;❌&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;a href="https://virtualenv.pypa.io/en/latest/" target="_blank" rel="noopener"&gt;Virtualenv&lt;/a&gt;&lt;/td&gt;&#10;&lt;td&gt;Env management (pre-3.3)&lt;/td&gt;&#10;&lt;td&gt;3rd Party&lt;/td&gt;&#10;&lt;td&gt;❌&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;a href="https://brew.sh/" target="_blank" rel="noopener"&gt;Homebrew&lt;/a&gt;&lt;/td&gt;&#10;&lt;td&gt;macOS/Linux package manager&lt;/td&gt;&#10;&lt;td&gt;3rd Party&lt;/td&gt;&#10;&lt;td&gt;✅&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;a href="https://prefix.dev/docs/pixi/overview" target="_blank" rel="noopener"&gt;Pixi&lt;/a&gt;&lt;/td&gt;&#10;&lt;td&gt;Fast Conda alternative&lt;/td&gt;&#10;&lt;td&gt;3rd Party&lt;/td&gt;&#10;&lt;td&gt;✅&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;a href="https://github.com/astral-sh/uv" target="_blank" rel="noopener"&gt;uv&lt;/a&gt;&lt;/td&gt;&#10;&lt;td&gt;Fast pip and venv replacement&lt;/td&gt;&#10;&lt;td&gt;3rd Party&lt;/td&gt;&#10;&lt;td&gt;❌&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;System Package Managers&lt;/td&gt;&#10;&lt;td&gt;OS-based package management&lt;/td&gt;&#10;&lt;td&gt;Native&lt;/td&gt;&#10;&lt;td&gt;✅&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Table 2: Python Environment Management Tools&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&#10;&lt;div class="table-wrap"&gt;&#10;&lt;table&gt;&#10;&lt;thead&gt;&#10;&lt;tr&gt;&#10;&lt;th&gt;Tool&lt;/th&gt;&#10;&lt;th&gt;Ease of Use&lt;/th&gt;&#10;&lt;th&gt;Isolation&lt;/th&gt;&#10;&lt;th&gt;Cross-Platform Support&lt;/th&gt;&#10;&lt;th&gt;Community &amp;amp; Support&lt;/th&gt;&#10;&lt;th&gt;Performance&lt;/th&gt;&#10;&lt;/tr&gt;&#10;&lt;/thead&gt;&#10;&lt;tbody&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;a href="https://github.com/pyenv/pyenv" target="_blank" rel="noopener"&gt;Pyenv&lt;/a&gt;&lt;/td&gt;&#10;&lt;td&gt;Medium&lt;/td&gt;&#10;&lt;td&gt;✅&lt;/td&gt;&#10;&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;&#10;&lt;td&gt;Good&lt;/td&gt;&#10;&lt;td&gt;Good&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;a href="https://pipenv.pypa.io/en/latest/" target="_blank" rel="noopener"&gt;Pipenv&lt;/a&gt;&lt;/td&gt;&#10;&lt;td&gt;Easy&lt;/td&gt;&#10;&lt;td&gt;✅&lt;/td&gt;&#10;&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;&#10;&lt;td&gt;Good&lt;/td&gt;&#10;&lt;td&gt;Good&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;a href="https://docs.conda.io/en/latest/" target="_blank" rel="noopener"&gt;Conda&lt;/a&gt;&lt;/td&gt;&#10;&lt;td&gt;Easy&lt;/td&gt;&#10;&lt;td&gt;✅&lt;/td&gt;&#10;&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;&#10;&lt;td&gt;Excellent&lt;/td&gt;&#10;&lt;td&gt;Average&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;a href="https://mamba.readthedocs.io/en/latest/" target="_blank" rel="noopener"&gt;Mamba&lt;/a&gt;&lt;/td&gt;&#10;&lt;td&gt;Easy&lt;/td&gt;&#10;&lt;td&gt;✅&lt;/td&gt;&#10;&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;&#10;&lt;td&gt;Growing&lt;/td&gt;&#10;&lt;td&gt;Fast&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;a href="https://mamba.readthedocs.io/en/latest/user_guide/micromamba.html" target="_blank" rel="noopener"&gt;Micromamba&lt;/a&gt;&lt;/td&gt;&#10;&lt;td&gt;Medium&lt;/td&gt;&#10;&lt;td&gt;✅&lt;/td&gt;&#10;&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;&#10;&lt;td&gt;Growing&lt;/td&gt;&#10;&lt;td&gt;Fast&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;a href="https://www.docker.com/" target="_blank" rel="noopener"&gt;Docker&lt;/a&gt;&lt;/td&gt;&#10;&lt;td&gt;Hard&lt;/td&gt;&#10;&lt;td&gt;✅&lt;/td&gt;&#10;&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;&#10;&lt;td&gt;Excellent&lt;/td&gt;&#10;&lt;td&gt;Good&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;a href="https://docs.python.org/3/library/venv.html" target="_blank" rel="noopener"&gt;Venv&lt;/a&gt;&lt;/td&gt;&#10;&lt;td&gt;Easy&lt;/td&gt;&#10;&lt;td&gt;✅&lt;/td&gt;&#10;&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;&#10;&lt;td&gt;Excellent&lt;/td&gt;&#10;&lt;td&gt;Good&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;a href="https://virtualenv.pypa.io/en/latest/" target="_blank" rel="noopener"&gt;Virtualenv&lt;/a&gt;&lt;/td&gt;&#10;&lt;td&gt;Medium&lt;/td&gt;&#10;&lt;td&gt;✅&lt;/td&gt;&#10;&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;&#10;&lt;td&gt;Good&lt;/td&gt;&#10;&lt;td&gt;Good&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;a href="https://brew.sh/" target="_blank" rel="noopener"&gt;Homebrew&lt;/a&gt;&lt;/td&gt;&#10;&lt;td&gt;Easy&lt;/td&gt;&#10;&lt;td&gt;❌&lt;/td&gt;&#10;&lt;td&gt;macOS, Linux&lt;/td&gt;&#10;&lt;td&gt;Excellent&lt;/td&gt;&#10;&lt;td&gt;Good&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;a href="https://prefix.dev/docs/pixi/overview" target="_blank" rel="noopener"&gt;Pixi&lt;/a&gt;&lt;/td&gt;&#10;&lt;td&gt;Easy&lt;/td&gt;&#10;&lt;td&gt;✅&lt;/td&gt;&#10;&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;&#10;&lt;td&gt;Growing&lt;/td&gt;&#10;&lt;td&gt;Fast&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;a href="https://github.com/astral-sh/uv" target="_blank" rel="noopener"&gt;uv&lt;/a&gt;&lt;/td&gt;&#10;&lt;td&gt;Easy&lt;/td&gt;&#10;&lt;td&gt;✅&lt;/td&gt;&#10;&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;&#10;&lt;td&gt;Growing&lt;/td&gt;&#10;&lt;td&gt;Very Fast&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;System Package Managers&lt;/td&gt;&#10;&lt;td&gt;Easy&lt;/td&gt;&#10;&lt;td&gt;❌&lt;/td&gt;&#10;&lt;td&gt;Depends on OS&lt;/td&gt;&#10;&lt;td&gt;Depends on OS&lt;/td&gt;&#10;&lt;td&gt;Good&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Now that we&amp;rsquo;ve seen the alternatives, let&amp;rsquo;s dive into setting up environments with the help of Conda and Mamba.&lt;/p&gt;&#10;&lt;h3 id="-pyenv"&gt;🐍 Pyenv &lt;a class="anchor" href="#-pyenv" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;A popular tool that enables you to easily manage multiple Python versions on a single system.&#10;It allows you to switch between different Python versions without interfering with system-level installations.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Setup Instructions&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Install pyenv:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;macOS: &lt;code&gt;brew install pyenv&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Ubuntu: &lt;code&gt;curl https://pyenv.run | bash&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Add the following lines to your shell configuration file (e.g., &lt;code&gt;~/.bashrc&lt;/code&gt;, &lt;code&gt;~/.zshrc&lt;/code&gt;):&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.pyenv/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;pyenv init -&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Restart your shell or run &lt;code&gt;source ~/.bashrc&lt;/code&gt; (or &lt;code&gt;source ~/.zshrc&lt;/code&gt;)&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Install a specific Python version: &lt;code&gt;pyenv install 3.8.5&lt;/code&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Set the Python version for your project: &lt;code&gt;pyenv local 3.8.5&lt;/code&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="-pipenv"&gt;📦 Pipenv &lt;a class="anchor" href="#-pipenv" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;A package management and virtual environment management tool that combines the functionality of pip and virtualenv.&#10;It streamlines the process of installing and managing packages within isolated environments.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Setup Instructions&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Install pipenv: &lt;code&gt;pip install --user pipenv&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Change to your project directory: &lt;code&gt;cd my_project&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Initialize a new virtual environment: &lt;code&gt;pipenv --python 3.8&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Install packages: &lt;code&gt;pipenv install &amp;lt;package_name&amp;gt;&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Activate the environment: &lt;code&gt;pipenv shell&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="-conda"&gt;🌐 Conda &lt;a class="anchor" href="#-conda" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;A cross-platform package manager and environment management tool, often used in conjunction with Anaconda and Miniconda.&#10;Conda simplifies the installation and management of packages and dependencies, especially for data science and machine learning projects.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Setup Instructions&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Download and install Miniconda: &lt;a href="https://github.com/conda-forge/miniforge#miniforge3" target="_blank" rel="noopener"&gt;https://github.com/conda-forge/miniforge#miniforge3&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Create a new environment: &lt;code&gt;conda create --name my_env python=3.8&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Activate the environment: &lt;code&gt;conda activate my_env&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Install packages: &lt;code&gt;conda install &amp;lt;package_name&amp;gt;&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="-mamba"&gt;🚀 Mamba &lt;a class="anchor" href="#-mamba" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;Mamba is a fast, drop-in replacement for Conda, designed to resolve and install packages more quickly.&#10;It uses the same Conda repositories and environment files, making it easy to switch between the two.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Setup Instructions&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Install Mambaforge with Mamba pre-installed:&lt;/p&gt;&#10;&lt;p&gt;Download and install Mambaforge with Mamba for your platform: &lt;a href="https://github.com/conda-forge/miniforge#mambaforge" target="_blank" rel="noopener"&gt;https://github.com/conda-forge/miniforge#mambaforge&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;Alternatively, you can install Mamba within an existing Conda environment:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;conda install -c conda-forge mamba&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Create a new environment with Mamba:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mamba create --name my_env &lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3.8&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Activate the environment:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mamba activate my_env&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Install packages using Mamba:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mamba install &amp;lt;package_name&amp;gt; -c conda-forge&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;By using Mamba instead of Conda, you can significantly speed up environment resolution and package installation while still benefiting from the Conda ecosystem.&lt;/p&gt;&#10;&lt;h3 id="-micromamba"&gt;🌌 Micromamba &lt;a class="anchor" href="#-micromamba" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;Micromamba is a lightweight and fast alternative to the Mamba package manager.&#10;It is a statically linked C++ executable that doesn&amp;rsquo;t require a base environment or come with a default Python version.&#10;It supports a subset of Mamba and Conda commands and is designed to resolve and install packages quickly.&#10;For those who need Conda features, you can install Conda using Micromamba with &lt;code&gt;micromamba install conda&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Setup Instructions&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Download the appropriate Micromamba binary for your platform from &lt;a href="https://github.com/mamba-org/mamba/releases" target="_blank" rel="noopener"&gt;the official repository&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Make the binary executable and move it to a directory in your &lt;code&gt;PATH&lt;/code&gt;:&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;chmod +x micromamba mv micromamba /usr/local/bin/&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;Create a new environment with Micromamba:&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;micromamba create -n my_env python -c conda-forge&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;Activate the environment:&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;micromamba activate my_env&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;Install packages using Micromamba:&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;micromamba install &amp;lt;package_name&amp;gt; -c conda-forge&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;If you need Conda&amp;rsquo;s features, you can install Conda within your Micromamba environment:&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;micromamba install conda&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;For example, if you need nb_conda_kernels to use your Micromamba environment in JupyterLab:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;micromamba install conda nb_conda_kernels&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And you are set up!&lt;/p&gt;&#10;&lt;p&gt;Micromamba is the perfect solution for those who want a fast and lightweight package manager while still being able to access Conda&amp;rsquo;s features when needed.&lt;/p&gt;&#10;&lt;h3 id="-docker"&gt;🐳 Docker &lt;a class="anchor" href="#-docker" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;A containerization platform that allows you to create lightweight, portable environments called containers.&#10;With Docker, you can package your Python application along with its dependencies, ensuring consistent execution across different systems.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Setup Instructions&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Install Docker: &lt;a href="https://docs.docker.com/get-docker/" target="_blank" rel="noopener"&gt;https://docs.docker.com/get-docker/&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Create a &lt;code&gt;Dockerfile&lt;/code&gt; in your project directory with the following contents:&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;FROM python:3.11&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;WORKDIR /app&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;COPY requirements.txt .&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;RUN pip install --no-cache-dir -r requirements.txt&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;COPY . .&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;CMD &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;python&amp;#34;&lt;/span&gt;, &lt;span class="s2"&gt;&amp;#34;your_script.py&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;Build the Docker image: &lt;code&gt;docker build -t my_project .&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Run the Docker container: &lt;code&gt;docker run my_project&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="-venv"&gt;🏞️ Venv &lt;a class="anchor" href="#-venv" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;A built-in Python module available since Python 3.3, which allows you to create lightweight virtual environments.&#10;Venv simplifies the process of creating isolated environments for different projects, ensuring that package dependencies don&amp;rsquo;t conflict with each other.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Setup Instructions&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Create a new virtual environment: &lt;code&gt;python3 -m venv my_env&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Activate the environment:&#10;&lt;ul&gt;&#10;&lt;li&gt;macOS/Linux: &lt;code&gt;source my_env/bin/activate&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Windows: &lt;code&gt;my_envScriptsactivate&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Install packages: &lt;code&gt;pip install &amp;lt;package_name&amp;gt;&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="-virtualenv"&gt;🌐 Virtualenv &lt;a class="anchor" href="#-virtualenv" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;Virtualenv is a third-party Python environment management tool that predates the built-in &lt;code&gt;venv&lt;/code&gt; module. It allows you to create isolated environments for different projects, ensuring that package dependencies don&amp;rsquo;t conflict with each other. Virtualenv is compatible with Python 2.7 and later versions, whereas &lt;code&gt;venv&lt;/code&gt; is only available since Python 3.3.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Setup Instructions&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Install virtualenv: &lt;code&gt;pip install virtualenv&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Create a new virtual environment: &lt;code&gt;virtualenv my_env&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Activate the environment:&#10;&lt;ul&gt;&#10;&lt;li&gt;macOS/Linux: &lt;code&gt;source my_env/bin/activate&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Windows: &lt;code&gt;my_env\Scripts\activate&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Install packages: &lt;code&gt;pip install &amp;lt;package_name&amp;gt;&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="-homebrew"&gt;🍺 Homebrew &lt;a class="anchor" href="#-homebrew" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;Homebrew is a popular package manager for macOS and Linux that simplifies the installation and management of software. Though it is not a dedicated Python environment management tool, it does offer support for installing and managing multiple Python versions alongside other software.&lt;/p&gt;&#10;&lt;p&gt;Homebrew can be useful for installing and managing Python versions for system-level usage, but it&amp;rsquo;s not the best choice for managing isolated project-specific dependencies. For project-specific dependency management, it&amp;rsquo;s recommended to use other tools like venv, virtualenv, or Conda in conjunction with Homebrew-installed Python.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Setup Instructions&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Install Homebrew (if not already installed):&#10;&lt;ul&gt;&#10;&lt;li&gt;macOS: Follow the instructions at &lt;a href="https://brew.sh/" target="_blank" rel="noopener"&gt;https://brew.sh&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Linux: Follow the instructions at &lt;a href="https://docs.brew.sh/Homebrew-on-Linux" target="_blank" rel="noopener"&gt;https://docs.brew.sh/Homebrew-on-Linux&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Install a specific Python version:&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew install python@3.11&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;Add the installed Python version to your &lt;code&gt;PATH&lt;/code&gt; by adding the following line to your shell configuration file (e.g., &lt;code&gt;~/.bashrc&lt;/code&gt;, &lt;code&gt;~/.zshrc&lt;/code&gt;):&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/usr/local/opt/python@3.11/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;Restart your shell or run &lt;code&gt;source ~/.bashrc&lt;/code&gt; (or &lt;code&gt;source ~/.zshrc&lt;/code&gt;) to apply the changes.&lt;/li&gt;&#10;&lt;li&gt;Verify the Python version: &lt;code&gt;python3.11 --version&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Keep in mind that Homebrew is mainly for installing and managing software at the system level. To manage project-specific dependencies and create isolated environments, combine Homebrew with other tools like venv or virtualenv for better dependency management.&lt;/p&gt;&#10;&lt;h3 id="-system-package-managers"&gt;💻 System package managers &lt;a class="anchor" href="#-system-package-managers" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;Some operating systems have their own package managers (e.g., apt for Ubuntu, pacman for Arch Linux, or Homebrew for macOS) that can be used to install and manage Python environments.&#10;However, using system package managers may lead to conflicts with other system packages and is generally not recommended for managing project-specific dependencies.&lt;/p&gt;&#10;&lt;p&gt;I will skip the setup instructions for this one, as it is just not recommended to do this!&lt;/p&gt;&#10;&lt;h3 id="-pixi"&gt;🌟 Pixi &lt;a class="anchor" href="#-pixi" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;Pixi is a new package manager built in Rust that uses Conda-forge packages but aims to be significantly faster and simpler than Conda.&#10;It&amp;rsquo;s developed by prefix.dev (the team behind Mamba) and focuses on providing a more streamlined experience.&#10;It is inspired by tools like Cargo, nix, and npm and aims to provide a modern package management experience for Python developers.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Setup Instructions&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Install pixi following instructions at &lt;a href="https://prefix.dev/docs/pixi/installation" target="_blank" rel="noopener"&gt;prefix.dev&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Initialize a new project:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pixi init my-project&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; my-project&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Add dependencies:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pixi add python numpy pandas&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Run Python in the environment:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pixi run python&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Create and run tasks (defined in pixi.toml):&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Add a task&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pixi task add hello &lt;span class="s2"&gt;&amp;#34;python hello_world.py&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Run the task&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pixi run hello&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Start an interactive shell in the environment:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pixi shell&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Pixi can also be used as a global installation tool:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Install tools globally&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pixi global install starship&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pixi global install fish&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Create a named environment with multiple packages&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pixi global install --environment data-science-env &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --expose python --expose jupyter &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; python jupyter numpy pandas&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Key features:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Fast package resolution and installation&lt;/li&gt;&#10;&lt;li&gt;Uses Conda-forge packages&lt;/li&gt;&#10;&lt;li&gt;Built-in task runner&lt;/li&gt;&#10;&lt;li&gt;Global package installation support&lt;/li&gt;&#10;&lt;li&gt;GitHub Actions integration with automatic caching&lt;/li&gt;&#10;&lt;li&gt;Simple and intuitive CLI&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="-uv"&gt;⚡ uv &lt;a class="anchor" href="#-uv" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;uv is an extremely fast Python package installer and resolver written in Rust.&#10;It can be used as a drop-in replacement for pip and can generate virtual environments.&#10;While it is quite new it is already being used by many developers due to its speed and ease of use.&#10;Besides being fast, it is awesome because it is a single binary that can be used to also bootstrap Python itself.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Setup Instructions&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;a href="https://docs.astral.sh/uv/getting-started/installation/" target="_blank" rel="noopener"&gt;Install uv&lt;/a&gt;:&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -LsSf https://astral.sh/uv/install.sh &lt;span class="p"&gt;|&lt;/span&gt; sh&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;Create a new virtual environment:&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uv venv&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;Activate the environment:&#10;&lt;ul&gt;&#10;&lt;li&gt;Unix/macOS: &lt;code&gt;source .venv/bin/activate&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Windows: &lt;code&gt;.venv\Scripts\activate&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Install packages (much faster than pip):&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uv pip install numpy pandas&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;It will automatically maintain a &lt;code&gt;uv.lock&lt;/code&gt; file to ensure deterministic builds.&#10;Install this lock file with &lt;code&gt;uv sync&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Key features of uv:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Up to 10x faster than pip&lt;/li&gt;&#10;&lt;li&gt;Built-in virtual environment support&lt;/li&gt;&#10;&lt;li&gt;Compatible with pip&amp;rsquo;s CLI interface&lt;/li&gt;&#10;&lt;li&gt;Global package caching&lt;/li&gt;&#10;&lt;li&gt;Deterministic builds&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;There is much more to uv, so check out the &lt;a href="https://docs.astral.sh/uv/" target="_blank" rel="noopener"&gt;documentation&lt;/a&gt; for more details.&lt;/p&gt;&#10;&lt;h2 id="3-conclusion-"&gt;3. Conclusion 🏁 &lt;a class="anchor" href="#3-conclusion-" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;The choice you make for managing your Python environments ultimately depends on your use case.&#10;If you don&amp;rsquo;t have to frequently deal with non-Python dependencies, I would recommend using &lt;code&gt;uv&lt;/code&gt; for its speed and simplicity.&#10;However, because I often work with projects that require non-Python dependencies, I&amp;rsquo;ve found that Micromamba is the perfect fit for me.&lt;/p&gt;&#10;&lt;p&gt;That being said, each tool has its own strengths and weaknesses, and it&amp;rsquo;s important to choose the one that works best for your specific needs.&#10;I hope this journey through Python environment management has been helpful and informative.&#10;Good luck in finding your perfect Python environment workflow! 🐍&lt;/p&gt;&#10;&lt;h2 id="4-further-reading-"&gt;4. Further Reading 📚 &lt;a class="anchor" href="#4-further-reading-" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Here are some resources for further exploration:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/pyenv/pyenv" target="_blank" rel="noopener"&gt;&lt;strong&gt;Pyenv&lt;/strong&gt;&lt;/a&gt;: Pyenv GitHub repository and documentation&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://pipenv.pypa.io/en/latest/" target="_blank" rel="noopener"&gt;&lt;strong&gt;Pipenv&lt;/strong&gt;&lt;/a&gt;: Pipenv documentation&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://docs.conda.io/en/latest/" target="_blank" rel="noopener"&gt;&lt;strong&gt;Conda&lt;/strong&gt;&lt;/a&gt;: Conda documentation&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://mamba.readthedocs.io/en/latest/" target="_blank" rel="noopener"&gt;&lt;strong&gt;Mamba&lt;/strong&gt;&lt;/a&gt;: Mamba documentation&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://mamba.readthedocs.io/en/latest/user_guide/micromamba.html" target="_blank" rel="noopener"&gt;&lt;strong&gt;Micromamba&lt;/strong&gt;&lt;/a&gt;: Micromamba documentation&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://docs.python.org/3/library/venv.html" target="_blank" rel="noopener"&gt;&lt;strong&gt;venv&lt;/strong&gt;&lt;/a&gt;: venv documentation&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://virtualenv.pypa.io/en/latest/" target="_blank" rel="noopener"&gt;&lt;strong&gt;virtualenv&lt;/strong&gt;&lt;/a&gt;: virtualenv documentation&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.anaconda.com/" target="_blank" rel="noopener"&gt;&lt;strong&gt;Anaconda&lt;/strong&gt;&lt;/a&gt;: Anaconda homepage&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://docs.conda.io/en/latest/miniconda.html" target="_blank" rel="noopener"&gt;&lt;strong&gt;Miniconda&lt;/strong&gt;&lt;/a&gt;: Miniconda documentation&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://brew.sh/" target="_blank" rel="noopener"&gt;&lt;strong&gt;Homebrew&lt;/strong&gt;&lt;/a&gt;: Homebrew homepage&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://prefix.dev/docs/pixi/overview" target="_blank" rel="noopener"&gt;&lt;strong&gt;Pixi&lt;/strong&gt;&lt;/a&gt;: Pixi documentation&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://docs.astral.sh/uv/" target="_blank" rel="noopener"&gt;&lt;strong&gt;uv&lt;/strong&gt;&lt;/a&gt;: uv documentation&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>UniDep 🧬</title><link>https://www.nijho.lt/post/advent-of-open-source/13-unidep/</link><pubDate>Fri, 13 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/13-unidep/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;Ever tried to set up a Python project that needs both pip and conda packages? Then you know the pain of maintaining multiple requirement files. &lt;a href="https://github.com/basnijholt/unidep" target="_blank" rel="noopener"&gt;UniDep&lt;/a&gt; was born from years of frustration with this exact problem.&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;While working on large monorepos with hundreds of dependencies, I kept running into the same issue: maintaining separate files for conda (&lt;code&gt;environment.yml&lt;/code&gt;), pip (&lt;code&gt;requirements.txt&lt;/code&gt;), and Python packaging (&lt;code&gt;pyproject.toml&lt;/code&gt;). Coming from languages like Rust, where package management is a joy, Python&amp;rsquo;s fragmented ecosystem can feel overwhelming, especially to newcomers (obligatory &lt;a href="https://xkcd.com/1987/" target="_blank" rel="noopener"&gt;xkcd.com/1987&lt;/a&gt;).&lt;/p&gt;&#10;&lt;p&gt;What started as a simple tool to avoid duplicate dependency definitions turned into something much more powerful. In enterprise environments, it&amp;rsquo;s common to find monorepos with over 1000 dependencies spread across dozens of packages. Setting up these environments traditionally involves following lengthy setup guides, installing packages in the correct order, and hoping nothing breaks.&lt;/p&gt;&#10;&lt;p&gt;Then came the &amp;ldquo;aha&amp;rdquo; moment - what if we could install everything with a single command? After implementing UniDep, those complex setup guides became a one-liner: &lt;code&gt;unidep install-all -e&lt;/code&gt;. It installs all dependencies and sets up every package in the monorepo in editable mode. What used to take hours now takes minutes.&lt;/p&gt;&#10;&lt;figure id="figure-the-difficulty-of-managing-pip-and-conda"&gt;&lt;img src="https://www.nijho.lt/post/advent-of-open-source/13-unidep/meme_hu_1916102bc051ce0e.webp" srcset="https://www.nijho.lt/post/advent-of-open-source/13-unidep/meme_hu_992ea341768024aa.webp 400w, https://www.nijho.lt/post/advent-of-open-source/13-unidep/meme_hu_1916102bc051ce0e.webp 760w, https://www.nijho.lt/post/advent-of-open-source/13-unidep/meme_hu_283eb791a4b7e11e.webp 980w" sizes="auto, (max-width: 760px) 100vw, 720px" width="760" height="447" alt="Meme with the text &amp;#39;ONE DOES NOT SIMPLY MANAGE BOTH PIP AND CONDA DEPENDENCIES&amp;#39; featuring Sean Bean as Boromir from The Lord of the Rings." loading="lazy" data-zoomable&gt;&lt;figcaption&gt;The difficulty of managing pip and conda&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Single source of truth for all dependencies&lt;/li&gt;&#10;&lt;li&gt;Supports both &lt;code&gt;requirements.yaml&lt;/code&gt; and &lt;code&gt;pyproject.toml&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Handles platform-specific dependencies&lt;/li&gt;&#10;&lt;li&gt;Generates conda-lock files both for entire monorepos and fully consistent lock files for its individual packages&lt;/li&gt;&#10;&lt;li&gt;Works with pip, conda, and mamba&lt;/li&gt;&#10;&lt;li&gt;Monorepo-friendly with local dependency support&lt;/li&gt;&#10;&lt;li&gt;Integrates with setuptools and hatchling&lt;/li&gt;&#10;&lt;li&gt;≥99% test coverage, fully-typed&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-impact"&gt;📊 Impact &lt;a class="anchor" href="#-impact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;221 GitHub stars&lt;/li&gt;&#10;&lt;li&gt;Featured on &lt;a href="https://www.youtube.com/live/PRaTs3PnJvI?si=UrVozo81Pj8WcyXh&amp;amp;t=489" target="_blank" rel="noopener"&gt;Python Bytes podcast&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Saves countless hours of dependency management&lt;/li&gt;&#10;&lt;li&gt;Makes complex project setups accessible&lt;/li&gt;&#10;&lt;li&gt;Actively maintained with 70 releases&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-challenges-and-solutions"&gt;🎯 Challenges and Solutions &lt;a class="anchor" href="#-challenges-and-solutions" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Different package naming between pip and conda&lt;/li&gt;&#10;&lt;li&gt;Platform-specific dependencies&lt;/li&gt;&#10;&lt;li&gt;Cross-platform compatibility (Linux, Windows, MacOS)&lt;/li&gt;&#10;&lt;li&gt;Supporting pip-only workflows&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Good developer experience is worth the effort&lt;/li&gt;&#10;&lt;li&gt;Sometimes you need to build a bridge between existing tools&lt;/li&gt;&#10;&lt;li&gt;The best tools make hard things easy&lt;/li&gt;&#10;&lt;li&gt;Dependency resolution is hard but can be simple for users&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="-future-plans"&gt;🔮 Future Plans &lt;a class="anchor" href="#-future-plans" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Adding Pixi support - allowing seamless migration to Pixi without changing config files&lt;/li&gt;&#10;&lt;li&gt;Eventually, Pixi might make UniDep obsolete (and that&amp;rsquo;s okay!)&lt;/li&gt;&#10;&lt;li&gt;For now, UniDep bridges the gap between pip and conda, making life easier for developers&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Want simpler dependency management? Check out &lt;a href="https://github.com/basnijholt/unidep" target="_blank" rel="noopener"&gt;UniDep on GitHub&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #Python #DevTools #Programming #PackageManagement #Conda&lt;/p&gt;&#10;</description></item><item><title>conda-forge Contributions 🐍</title><link>https://www.nijho.lt/post/advent-of-open-source/11-conda-forge-contributions/</link><pubDate>Wed, 11 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/11-conda-forge-contributions/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;Unlike other projects in this advent calendar, I want to celebrate being part of something much bigger: the &lt;a href="https://conda-forge.org/" target="_blank" rel="noopener"&gt;conda-forge&lt;/a&gt; community, where thousands of volunteers maintain the packages that power scientific Python.&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;I started with conda-forge in 2016 during my PhD work with Kwant, a quantum transport simulator. Installing it was a nightmare - it required system-level numerical libraries like MUMPS and Scotch which required a special config file to link. When I discovered conda-forge and its mission to make scientific software installation painless, I knew this was the solution!&lt;/p&gt;&#10;&lt;figure id="figure-the-humble-work-of-conda-forge-maintainers"&gt;&lt;img src="https://www.nijho.lt/post/advent-of-open-source/11-conda-forge-contributions/meme_hu_227391ecf0bd252.webp" srcset="https://www.nijho.lt/post/advent-of-open-source/11-conda-forge-contributions/meme_hu_9b170f53ebe4dc1b.webp 400w, https://www.nijho.lt/post/advent-of-open-source/11-conda-forge-contributions/meme_hu_227391ecf0bd252.webp 760w, https://www.nijho.lt/post/advent-of-open-source/11-conda-forge-contributions/meme_hu_95b0c28d6df1a41a.webp 978w" sizes="auto, (max-width: 760px) 100vw, 720px" width="760" height="586" alt="Meme showing a farmer standing in a field, with the text &amp;#39;MAINTAINING CONDA-FORGE PACKAGES FOR THE SCIENTIFIC COMMUNITY&amp;#39; above and &amp;#39;It ain&amp;#39;t much, but it&amp;#39;s honest work&amp;#39; below." loading="lazy" data-zoomable&gt;&lt;figcaption&gt;The humble work of conda-forge maintainers&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Over the past 8 years, I&amp;rsquo;ve maintained over 40 build recipes, including:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Major scientific packages like &lt;a href="https://vtk.org/" target="_blank" rel="noopener"&gt;VTK&lt;/a&gt;, &lt;a href="https://mumps-solver.org/" target="_blank" rel="noopener"&gt;MUMPS&lt;/a&gt;, and &lt;a href="https://www.labri.fr/perso/pelegrin/scotch/" target="_blank" rel="noopener"&gt;Scotch&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Quantum physics tools like &lt;a href="https://quantumai.google/qsim" target="_blank" rel="noopener"&gt;Qsim&lt;/a&gt;, &lt;a href="https://github.com/microsoft/Qcodes" target="_blank" rel="noopener"&gt;Qcodes&lt;/a&gt;, and &lt;a href="https://kwant-project.org/" target="_blank" rel="noopener"&gt;Kwant&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Visualization libraries like &lt;a href="https://holoviews.org/" target="_blank" rel="noopener"&gt;HoloViews&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Plus all the packages I&amp;rsquo;ve authored myself&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;The real magic of conda-forge is its infrastructure:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Automated builds across Linux, macOS, and Windows&lt;/li&gt;&#10;&lt;li&gt;Strict dependency version management&lt;/li&gt;&#10;&lt;li&gt;Comprehensive CI/CD pipelines&lt;/li&gt;&#10;&lt;li&gt;Community-driven quality control&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-impact"&gt;📊 Impact &lt;a class="anchor" href="#-impact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;What makes conda-forge truly special is how initial contributions blossom into collaborative efforts. I&amp;rsquo;ve experienced this firsthand: after creating the initial MUMPS feedstock, 25 other contributors joined in, each bringing their unique expertise.&lt;/p&gt;&#10;&lt;p&gt;A recent experience also illustrates this: I added CUDA support to both &lt;a href="https://github.com/conda-forge/qsimcirq-feedstock/pull/3" target="_blank" rel="noopener"&gt;Google&amp;rsquo;s Qsim&lt;/a&gt; and &lt;a href="https://github.com/conda-forge/qiskit-aer-feedstock/pull/19" target="_blank" rel="noopener"&gt;IBM&amp;rsquo;s Cirq&lt;/a&gt; packages. Shortly after, &lt;a href="https://github.com/leofang" target="_blank" rel="noopener"&gt;Leo Fang&lt;/a&gt;, an engineer from NVIDIA who knows CUDA far better than I do, stepped up and made significant improvements. This is the beauty of open source - experts naturally gravitate to where they can make the biggest impact.&lt;/p&gt;&#10;&lt;p&gt;This collaborative approach solves a problem I&amp;rsquo;ve encountered at several companies: the tendency to maintain complicated build systems internally with custom hacks. Instead of each organization reinventing the wheel, conda-forge provides a shared platform where:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Maintenance burden is distributed&lt;/li&gt;&#10;&lt;li&gt;Expert knowledge flows freely&lt;/li&gt;&#10;&lt;li&gt;Build practices are standardized&lt;/li&gt;&#10;&lt;li&gt;Infrastructure is public and reusable&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-challenges-and-solutions"&gt;🎯 Challenges and Solutions &lt;a class="anchor" href="#-challenges-and-solutions" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Keeping up with upstream changes&lt;/li&gt;&#10;&lt;li&gt;Managing complex dependency trees&lt;/li&gt;&#10;&lt;li&gt;Cross-platform compatibility&lt;/li&gt;&#10;&lt;li&gt;Coordinating with upstream maintainers&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Community effort beats solo work for infrastructure&lt;/li&gt;&#10;&lt;li&gt;Automation is crucial for reliability&lt;/li&gt;&#10;&lt;li&gt;Small contributions compound over time&lt;/li&gt;&#10;&lt;li&gt;Being part of something bigger is rewarding&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Want to contribute to scientific Python? Check out &lt;a href="https://conda-forge.org/" target="_blank" rel="noopener"&gt;conda-forge&lt;/a&gt; and the &lt;a href="https://github.com/conda-forge/staged-recipes" target="_blank" rel="noopener"&gt;staged-recipes repository&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #Python #Scientific #Programming #Community&lt;/p&gt;&#10;</description></item><item><title>My favorite tools that keep my Python projects sane 🧰</title><link>https://www.nijho.lt/post/best-python-dev-tooling/</link><pubDate>Sat, 22 Apr 2023 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/best-python-dev-tooling/</guid><description>&lt;h2 id="-1-introduction"&gt;🌟 1. Introduction &lt;a class="anchor" href="#-1-introduction" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;As a passionate Python developer, I am always on the hunt for the best tools to streamline my development process and enhance the quality of my projects. Over time, I have discovered a set of tools that have become indispensable in my workflow, making my coding experience more efficient and enjoyable. Using the right developer tools for Python projects is crucial for maximizing productivity, ensuring code consistency, and maintaining high-quality code.&lt;/p&gt;&#10;&lt;p&gt;In this blog post, I will share these essential tools that I now use for every single new project. We will explore the benefits of each tool and discuss how they can help you supercharge your Python development. The tools covered in this post include &lt;a href="https://pre-commit.com/" target="_blank" rel="noopener"&gt;pre-commit&lt;/a&gt;, &lt;a href="https://black.readthedocs.io/en/stable/" target="_blank" rel="noopener"&gt;Black&lt;/a&gt;, &lt;a href="https://beta.ruff.rs/docs/rules/" target="_blank" rel="noopener"&gt;Ruff&lt;/a&gt;, &lt;a href="https://mypy.readthedocs.io/en/stable/" target="_blank" rel="noopener"&gt;Mypy&lt;/a&gt;, &lt;a href="https://versioningit.readthedocs.io/en/latest/" target="_blank" rel="noopener"&gt;Versioningit&lt;/a&gt;, &lt;a href="https://www.python.org/dev/peps/pep-0621/" target="_blank" rel="noopener"&gt;Pyproject.toml with PEP 621&lt;/a&gt;, &lt;a href="https://nox.thea.codes/en/stable/" target="_blank" rel="noopener"&gt;Nox&lt;/a&gt;, &lt;a href="https://docs.pytest.org/en/7.3.x/" target="_blank" rel="noopener"&gt;pytest&lt;/a&gt;, &lt;a href="https://docs.github.com/en/actions/learn-github-actions" target="_blank" rel="noopener"&gt;GitHub Actions CI matrix&lt;/a&gt;, and &lt;a href="https://readthedocs.org/" target="_blank" rel="noopener"&gt;Read the Docs&lt;/a&gt;, &lt;a href="https://www.sphinx-doc.org/" target="_blank" rel="noopener"&gt;Sphinx&lt;/a&gt;, &lt;a href="https://myst-nb.readthedocs.io/en/latest/" target="_blank" rel="noopener"&gt;MyST-NB&lt;/a&gt;, and &lt;a href="https://jupytext.readthedocs.io/en/latest/" target="_blank" rel="noopener"&gt;Jupytext notebooks&lt;/a&gt; for documentation. Let&amp;rsquo;s dive in and learn how to enhance your Python projects with these powerful tools!&lt;/p&gt;&#10;&lt;details class="toc" open&gt;&lt;summary&gt;Table of contents&lt;/summary&gt;&lt;nav id="TableOfContents"&gt;&#10; &lt;ul&gt;&#10; &lt;li&gt;&lt;a href="#-1-introduction"&gt;🌟 1. Introduction&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#-2-pre-commit"&gt;🛠️ 2. Pre-commit&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#-3-black-auto-formatting-for-python"&gt;🎨 3. Black: auto-formatting for Python&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#-4-ruff-the-fastest-python-linter-and-auto-formatter"&gt;🚀 4. Ruff: the fastest Python linter and auto-formatter&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#-5-mypy-static-type-checking-for-python-find-bugs-before-they-happen"&gt;🔍 5. Mypy: static type checking for Python (find bugs before they happen)&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#-6-nox-and-pytest-for-testing-a-powerful-combination"&gt;🧪 6. Nox and pytest for testing: a powerful combination&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#-7-versioningit-version-management-with-git-tags"&gt;🏷️ 7. Versioningit: version management with Git tags&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#-8-pyprojecttoml-and-pep-621-configuration-consolidation"&gt;📝 8. Pyproject.toml and PEP 621: Configuration Consolidation&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#-9-github-actions-ci-matrix-testing-on-multiple-platforms-and-auto-publishing-to-pypi"&gt;🌐 9. GitHub Actions CI Matrix: Testing on Multiple Platforms and Auto Publishing to PyPI&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#-10-read-the-docs-sphinx-myst-nb-and-jupytext-notebooks-for-documentation"&gt;📚 10. Read the Docs, Sphinx, MyST-NB, and Jupytext notebooks for documentation&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#-11-conclusion"&gt;🎉 11. Conclusion&lt;/a&gt;&lt;/li&gt;&#10; &lt;/ul&gt;&#10;&lt;/nav&gt;&lt;/details&gt;&#10;&#10;&lt;aside class="callout callout-note"&gt;&lt;svg class="ico" viewBox="0 0 256 256" aria-hidden="true"&gt;&lt;path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"/&gt;&lt;/svg&gt;&lt;div&gt;This post is based the way I develop Python projects. For full examples see &lt;a href="https://github.com/basnijholt/adaptive-scheduler/" target="_blank" rel="noopener"&gt;Adaptive Scheduler&lt;/a&gt; (full project), &lt;a href="https://github.com/basnijholt/markdown-code-runner" target="_blank" rel="noopener"&gt;&lt;code&gt;markdown-code-runner&lt;/code&gt;&lt;/a&gt; (for a single module package), and &lt;a href="https://github.com/python-adaptive/adaptive/" target="_blank" rel="noopener"&gt;Adaptive&lt;/a&gt; (for cool documentation and nox).&#10;Over time I might change my mind about some of the tools, but I will try to keep this post up-to-date.&lt;/div&gt;&lt;/aside&gt;&#10;&lt;h2 id="-2-pre-commit"&gt;🛠️ 2. Pre-commit &lt;a class="anchor" href="#-2-pre-commit" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;One of the most common challenges developers face when working on a project, whether alone or in a team, is maintaining code quality and consistency. As projects grow, it becomes increasingly difficult to enforce coding standards and catch issues before they make their way into the codebase. This is where pre-commit comes in.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://pre-commit.com/" target="_blank" rel="noopener"&gt;Pre-commit&lt;/a&gt; is a powerful tool that helps you manage and maintain code quality by automating checks before commits. It achieves this by setting up a series of hooks that run before each commit, ensuring that only code that meets your predefined standards is committed to the repository. By catching issues early, pre-commit helps you keep your code clean and consistent, making it easier to review and maintain.&lt;/p&gt;&#10;&lt;p&gt;One of the reasons pre-commit has gained widespread adoption is its flexibility and extensibility. You can choose from a vast range of built-in hooks, such as code formatters, linters, and security checkers, or even create your own custom hooks. This allows you to tailor pre-commit to the specific needs of your project, ensuring that it enforces the exact coding standards you require.&lt;/p&gt;&#10;&lt;p&gt;Using pre-commit is beneficial for both individual developers and teams. For solo developers, it helps maintain discipline and enforces coding standards, making it easier to onboard collaborators in the future. For teams, it streamlines the review process by catching issues before they are submitted for review, reducing the time spent on addressing code inconsistencies and style violations.&lt;/p&gt;&#10;&lt;p&gt;Let&amp;rsquo;s take a look at an example. Suppose your project follows the PEP 8 coding standard, and you want to ensure that all code committed to the repository adheres to these guidelines. You can configure pre-commit to run a PEP 8 linter, such as Flake8, before each commit. This way, any code that does not meet the PEP 8 standard will be flagged, and the commit will be blocked until the issues are resolved.&lt;/p&gt;&#10;&lt;p&gt;In summary, pre-commit is an invaluable tool for maintaining code quality and consistency in your Python projects. By automating checks before commits, it ensures that your codebase remains clean and adheres to your chosen standards, making your development process more efficient and enjoyable.&lt;/p&gt;&#10;&lt;p&gt;Adding pre-commit to your Python project is simple and straightforward. To get started, you&amp;rsquo;ll first need to install the pre-commit package using pip:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pip install pre-commit&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, create a configuration file named &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt; in your project&amp;rsquo;s root directory. This file will define the hooks you want to run before each commit. For example, to use Black for code formatting and Flake8 for linting, your configuration file might look like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;https://github.com/psf/black&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rev&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;stable&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;black&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;https://github.com/pycqa/flake8&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rev&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;master&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;flake8&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once you have set up the configuration file, run the following command to install the pre-commit hooks:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pre-commit install&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, every time you attempt to make a commit, pre-commit will run the specified hooks. If any issues are found, the commit will be blocked, and you&amp;rsquo;ll be prompted to fix the problems before trying again. To run pre-commit manually on all files in your repository, use the following command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pre-commit run --all-files&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;By incorporating pre-commit into your development process, you&amp;rsquo;ll be able to catch and fix issues before they make their way into your codebase, ensuring a consistent and high-quality Python project.&lt;/p&gt;&#10;&lt;h2 id="-3-black-auto-formatting-for-python"&gt;🎨 3. Black: auto-formatting for Python &lt;a class="anchor" href="#-3-black-auto-formatting-for-python" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;As a developer who has always been particular about coding style, I&amp;rsquo;ve had my fair share of discussions on the proper way to indent multiline statements and other formatting nuances. These conversations, although sometimes necessary, can take up valuable time that could be better spent on actual development. That&amp;rsquo;s where Black, the uncompromising code formatter, comes in and saves the day.&lt;/p&gt;&#10;&lt;p&gt;Adopting &lt;a href="https://black.readthedocs.io/en/stable/" target="_blank" rel="noopener"&gt;Black&lt;/a&gt; has made a significant impact on my development workflow. No more unnecessary discussions about formatting preferences; everyone on the team simply follows the rules set forth by Black. This consistency across the codebase not only makes the code more readable but also reduces the time spent on formatting debates.&lt;/p&gt;&#10;&lt;p&gt;One of the best aspects of Black is that you don&amp;rsquo;t need to worry about writing perfectly formatted code initially. You can write your code as quickly as you want, even if it&amp;rsquo;s poorly formatted, and then simply run Black to transform it into a beautifully formatted masterpiece. This can be a real time-saver, especially when working on large projects with many contributors.&lt;/p&gt;&#10;&lt;p&gt;To install Black, run the following command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pip install black&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can then format your code by running Black in the terminal:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;black your_file.py&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Alternatively, you can use the VS Code command palette to run Black on the currently open file. However, the most efficient way to use Black is in combination with pre-commit, as mentioned in the previous section. This way, your code is automatically formatted every time you commit, ensuring a consistent codebase without any extra effort.&lt;/p&gt;&#10;&lt;p&gt;In summary, Black is an invaluable tool for enforcing consistent code formatting throughout your Python projects. By adopting it and integrating it with pre-commit, you can save time and focus on what truly matters: writing great code.&lt;/p&gt;&#10;&lt;h2 id="-4-ruff-the-fastest-python-linter-and-auto-formatter"&gt;🚀 4. Ruff: the fastest Python linter and auto-formatter &lt;a class="anchor" href="#-4-ruff-the-fastest-python-linter-and-auto-formatter" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;In the past, I&amp;rsquo;ve used several tools to maintain code quality, such as isort, pyupgrade, flake8, and pydocstyle. However, I&amp;rsquo;ve recently discovered a powerful new tool called &lt;a href="https://beta.ruff.rs/docs/rules/" target="_blank" rel="noopener"&gt;Ruff&lt;/a&gt;), an extremely fast Python linter written in Rust. Ruff is not only capable of replacing all these tools but also executes much faster, which significantly improves the development workflow.&lt;/p&gt;&#10;&lt;p&gt;Ruff offers an impressive range of features and performance enhancements. Some of its standout features include:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;10-100x faster than existing linters&lt;/li&gt;&#10;&lt;li&gt;Over 500 built-in rules&lt;/li&gt;&#10;&lt;li&gt;Autofix support for automatic error correction&lt;/li&gt;&#10;&lt;li&gt;Built-in caching to avoid re-analyzing unchanged files&lt;/li&gt;&#10;&lt;li&gt;First-party editor integrations for VS Code and more&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Moreover, Ruff has gained widespread adoption in the Python community and is used in major open-source projects like Apache Airflow, FastAPI, Hugging Face, Pandas, and SciPy. The tool is backed by Astral, which provides active development and support.&lt;/p&gt;&#10;&lt;p&gt;One of the best aspects of Ruff is the native VS Code extension, which continuously scans your code and can fix problems as you write. This seamless integration makes it an indispensable part of any Python development workflow.&lt;/p&gt;&#10;&lt;p&gt;To install Ruff, simply run the following command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pip install ruff&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To use Ruff in your project, run the following command in the terminal:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ruff your_file.py&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For the best experience, I highly recommend using Ruff in conjunction with pre-commit, as mentioned earlier. This ensures that your code is always linted and formatted before committing, maintaining a consistent and high-quality codebase.&lt;/p&gt;&#10;&lt;p&gt;In conclusion, Ruff is an incredibly fast and powerful Python linter that streamlines the development process by consolidating multiple tools into a single, efficient package. By adopting Ruff and integrating it with pre-commit, you can enjoy a more productive and enjoyable Python development experience.&lt;/p&gt;&#10;&lt;aside class="callout callout-note"&gt;&lt;svg class="ico" viewBox="0 0 256 256" aria-hidden="true"&gt;&lt;path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"/&gt;&lt;/svg&gt;&lt;div&gt;Pro tip: Go extreme, and use &lt;code&gt;--select ALL&lt;/code&gt; to enable all (&amp;gt;500) rules. This will ensure that your code is always in top shape.&lt;/div&gt;&lt;/aside&gt;&#10;&lt;h2 id="-5-mypy-static-type-checking-for-python-find-bugs-before-they-happen"&gt;🔍 5. Mypy: static type checking for Python (find bugs before they happen) &lt;a class="anchor" href="#-5-mypy-static-type-checking-for-python-find-bugs-before-they-happen" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;As developers, we&amp;rsquo;re always looking for ways to improve the quality of our code and minimize the time spent on debugging. This is where &lt;a href="https://mypy.readthedocs.io/en/stable/" target="_blank" rel="noopener"&gt;Mypy&lt;/a&gt; comes in. Mypy is a powerful static type checker for Python, designed to catch potential runtime errors before they occur. By using Mypy, you can ensure that your code adheres to type hints and that it runs smoothly without unexpected issues.&lt;/p&gt;&#10;&lt;p&gt;Mypy offers several advantages to Python developers, including:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Early detection of potential type errors, preventing many common bugs&lt;/li&gt;&#10;&lt;li&gt;Improved code readability and self-documentation through type annotations&lt;/li&gt;&#10;&lt;li&gt;Easier refactoring and code maintenance, as type annotations provide clear contracts between functions and modules&lt;/li&gt;&#10;&lt;li&gt;Better collaboration and communication within teams, since type annotations make the code&amp;rsquo;s intent and expectations more explicit&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Adding Mypy to an existing project may initially seem time-consuming and frustrating, as it might flag numerous issues that require attention. However, once you&amp;rsquo;ve addressed these concerns and properly set up Mypy, the benefits far outweigh the initial effort. In fact, the time you invest in fixing type-related issues will quickly be recovered as Mypy helps you prevent bugs and reduce debugging time.&lt;/p&gt;&#10;&lt;p&gt;For a long time, I too ignored Mypy and only typed my code without actually checking it with the tool. But after truly embracing Mypy and integrating it into my development process, I can confidently say that I will never go back. The value it brings to the table in terms of code quality, readability, and reliability is simply too great to ignore.&lt;/p&gt;&#10;&lt;p&gt;By proactively identifying and resolving type-related issues, Mypy not only saves you precious debugging time but also enhances the overall quality of your codebase. As you continue to rely on Mypy, you&amp;rsquo;ll find that it becomes an indispensable part of your Python development toolkit, ensuring that your projects remain robust and maintainable.&lt;/p&gt;&#10;&lt;p&gt;To start using Mypy in your project, first install it with the following command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pip install mypy&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, you can run Mypy on your Python files by executing the following command in your terminal:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mypy your_file.py&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For even better integration, you can add Mypy as a pre-commit hook, ensuring that your code is always checked for type consistency before being committed. This further strengthens the code quality and helps prevent potential runtime errors.&lt;/p&gt;&#10;&lt;p&gt;In summary, incorporating Mypy into your Python development workflow adds an extra layer of safety and clarity, making it easier to write high-quality, reliable code. By combining Mypy with other tools like Ruff and pre-commit, you can create a robust development environment that promotes productivity and code quality.&lt;/p&gt;&#10;&lt;h2 id="-6-nox-and-pytest-for-testing-a-powerful-combination"&gt;🧪 6. Nox and pytest for testing: a powerful combination &lt;a class="anchor" href="#-6-nox-and-pytest-for-testing-a-powerful-combination" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;When developing software, it&amp;rsquo;s essential to ensure that your code is reliable and functions as expected. The key to achieving this goal is writing thorough tests, which not only help you identify issues early on but also prevent regressions in the future. While it&amp;rsquo;s often tempting to dive headfirst into coding, taking a step back and first writing tests for your solution can lead to more efficient and effective development. Even if this approach doesn&amp;rsquo;t come naturally, it&amp;rsquo;s crucial to write tests for every problem you encounter to keep your codebase stable and reliable.&lt;/p&gt;&#10;&lt;p&gt;In the realm of Python testing, &lt;a href="https://nox.thea.codes/en/stable/" target="_blank" rel="noopener"&gt;Nox&lt;/a&gt; and &lt;a href="https://docs.pytest.org" target="_blank" rel="noopener"&gt;pytest&lt;/a&gt; stand out as powerful tools that can greatly enhance your testing process. Nox is a versatile automation tool that simplifies running tests in multiple virtual environments, while pytest is a popular and feature-rich testing framework for Python. Together, these tools provide a formidable testing environment that simplifies test management and execution.&lt;/p&gt;&#10;&lt;p&gt;In the past, you may have used &lt;a href="https://tox.wiki/en/latest/" target="_blank" rel="noopener"&gt;Tox&lt;/a&gt;, a tool that manages test environments with a text-based configuration file (e.g., in &lt;code&gt;setup.cfg&lt;/code&gt; or &lt;code&gt;pyproject.toml&lt;/code&gt;). While Tox has served developers well for many years, including myself, Nox offers a more flexible alternative. Instead of relying on a text-based configuration, Nox takes advantage of a Python configuration file, providing virtually limitless configurability. This added flexibility makes it easy to create complex test matrices with different versions and dependencies, and even accommodate special conditions using simple if statements. After happily using Tox for years, I discovered Nox and found that it elevated my testing workflow to a whole new level.&lt;/p&gt;&#10;&lt;p&gt;To get started with Nox and pytest, create a basic &lt;code&gt;nox.py&lt;/code&gt; file and a sample test file for pytest. Here&amp;rsquo;s an example &lt;code&gt;nox.py&lt;/code&gt; configuration:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;nox&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@nox.session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;python&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;3.7&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;3.8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;3.9&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-r&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;requirements.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-r&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;test-requirements.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And an example test file for pytest:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pytest&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_addition&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_subtraction&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Beyond basic testing functionality, pytest boasts powerful features such as fixtures, mocking and patching, and parameterization. Pytest fixtures enable you to create reusable resources for your tests, while mocking and patching allow you to replace parts of your system with fake objects, isolating your tests from external dependencies. Parameterization, implemented using &lt;code&gt;pytest.mark.parametrize&lt;/code&gt;, enables running the same test with multiple sets of inputs and expected outputs.&lt;/p&gt;&#10;&lt;p&gt;In conclusion, leveraging Nox and pytest for testing offers numerous advantages for developers who strive to improve code reliability and automate their testing workflows. By harnessing Nox&amp;rsquo;s exceptional configurability and pytest&amp;rsquo;s extensive testing features, you&amp;rsquo;ll establish a robust testing environment that bolsters code quality and ensures optimal performance.&lt;/p&gt;&#10;&lt;h2 id="-7-versioningit-version-management-with-git-tags"&gt;🏷️ 7. Versioningit: version management with Git tags &lt;a class="anchor" href="#-7-versioningit-version-management-with-git-tags" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;(Optional, only related when making a Python package)&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;For developers seeking an automated and customizable package versioning solution, &lt;a href="https://versioningit.readthedocs.io/en/latest/" target="_blank" rel="noopener"&gt;Versioningit&lt;/a&gt; offers a powerful yet flexible approach. It is a setuptools plugin that automatically determines your package&amp;rsquo;s version based on your version control repository&amp;rsquo;s tags. The real advantage of Versioningit lies in its ability to customize the version format and even override separate functions used for version extraction and calculation.&lt;/p&gt;&#10;&lt;p&gt;Versioningit makes your release workflow extremely simple, especially if you have set up an automated build and twine PyPI upload pipeline. To create a new release, you only need to tag a version, and then click on the &amp;ldquo;Release&amp;rdquo; button in GitHub.&lt;/p&gt;&#10;&lt;p&gt;One of the major advantages of Versioningit is its minimal boilerplate code compared to other tools like &lt;a href="https://github.com/python-versioneer/python-versioneer" target="_blank" rel="noopener"&gt;Versioneer&lt;/a&gt;. Additionally, the Python &lt;code&gt;__version__&lt;/code&gt; string works seamlessly with editable installs (&lt;code&gt;pip install -e .&lt;/code&gt;), unlike &lt;a href="https://github.com/pypa/setuptools_scm" target="_blank" rel="noopener"&gt;setuptools_scm&lt;/a&gt;, which is a common issue faced by developers.&lt;/p&gt;&#10;&lt;p&gt;Over the years, I have explored many options for package versioning, including &lt;a href="https://github.com/python-versioneer/python-versioneer" target="_blank" rel="noopener"&gt;Versioneer&lt;/a&gt;, &lt;a href="https://github.com/jbweston/miniver" target="_blank" rel="noopener"&gt;Miniver&lt;/a&gt;, &lt;a href="https://github.com/pypa/setuptools_scm" target="_blank" rel="noopener"&gt;setuptools_scm&lt;/a&gt;, &lt;a href="https://github.com/peritus/bumpversion" target="_blank" rel="noopener"&gt;bumpversion&lt;/a&gt; (or &lt;a href="https://github.com/c4urself/bump2version" target="_blank" rel="noopener"&gt;bump2version&lt;/a&gt;), and manually setting everything. Versioningit stands out due to its simplicity, customization options, and compatibility with various workflows.&lt;/p&gt;&#10;&lt;p&gt;Some key features of Versioningit include:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Support for Git, modern Git archives, and Mercurial&lt;/li&gt;&#10;&lt;li&gt;Customizable version formatting using template strings&lt;/li&gt;&#10;&lt;li&gt;Optional writing of the final version and other details to a file for loading at runtime&lt;/li&gt;&#10;&lt;li&gt;Custom setuptools commands for inserting the final version and other details into a source file at build time&lt;/li&gt;&#10;&lt;li&gt;Customizable functions for VCS querying, tag-to-version calculation, version bumping, version formatting, and writing the version to a file&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;To use Versioningit in your project, simply add it to your project&amp;rsquo;s &lt;code&gt;pyproject.toml&lt;/code&gt; file in the &lt;code&gt;build-system.requires&lt;/code&gt; key. Then, create a &lt;code&gt;[tool.versioningit]&lt;/code&gt; table in your &lt;code&gt;pyproject.toml&lt;/code&gt; file. You can get up and running with just the minimal configuration - an empty table.&lt;/p&gt;&#10;&lt;p&gt;Once you have a &lt;code&gt;[tool.versioningit]&lt;/code&gt; table in your &lt;code&gt;pyproject.toml&lt;/code&gt; and your repository has at least one tag, building your project with &lt;code&gt;setuptools&lt;/code&gt; will automatically set your project&amp;rsquo;s version based on the latest tag in your Git repository. With Versioningit, you can customize the version format using placeholder strings, making it adaptable to your project&amp;rsquo;s specific needs.&lt;/p&gt;&#10;&lt;p&gt;In summary, Versioningit streamlines the package versioning process and simplifies the release workflow. By automating package versioning based on version control repository tags and providing extensive customization options, it helps make the management of package versions more efficient and less error-prone.&lt;/p&gt;&#10;&lt;h2 id="-8-pyprojecttoml-and-pep-621-configuration-consolidation"&gt;📝 8. Pyproject.toml and PEP 621: Configuration Consolidation &lt;a class="anchor" href="#-8-pyprojecttoml-and-pep-621-configuration-consolidation" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;The &lt;code&gt;pyproject.toml&lt;/code&gt; file plays a crucial role in consolidating configurations and setup for various tools in a Python project, such as Black, Ruff, Mypy, Versioningit, Nox, and pytest. This adherence to &lt;a href="https://www.python.org/dev/peps/pep-0621/" target="_blank" rel="noopener"&gt;PEP 621&lt;/a&gt; standards streamlines the development process by providing a single source of truth for multiple tools, eliminating the need for additional configuration files like &lt;code&gt;setup.cfg&lt;/code&gt; or &lt;code&gt;setup.py&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;By specifying packaging details directly in the &lt;code&gt;pyproject.toml&lt;/code&gt; file, you can efficiently manage your project&amp;rsquo;s dependencies and metadata. For example, the Adaptive Scheduler package demonstrates this approach with a comprehensive &lt;code&gt;pyproject.toml&lt;/code&gt; file containing all the necessary configurations.&lt;/p&gt;&#10;&lt;details&gt;&#10;&lt;summary&gt;(click to unfold):&lt;/summary&gt;&#10;&lt;!-- CODE:START --&gt;&#10;&lt;!-- import urllib.request --&gt;&#10;&lt;!-- url = 'https://raw.githubusercontent.com/basnijholt/adaptive-scheduler/main/pyproject.toml' --&gt;&#10;&lt;!-- print("```toml") --&gt;&#10;&lt;!-- with urllib.request.urlopen(url) as response: --&gt;&#10;&lt;!-- content = response.read() --&gt;&#10;&lt;!-- print(content.decode('utf-8')) --&gt;&#10;&lt;!-- print("```") --&gt;&#10;&lt;!-- CODE:END --&gt;&#10;&lt;!-- OUTPUT:START --&gt;&#10;&lt;!-- ⚠️ This content is auto-generated by `markdown-code-runner`. --&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;build-system&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;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;&#10;&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;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;wheel&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;versioningit&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;adaptive_scheduler&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Run many `adaptive.Learner`s on many cores (&amp;gt;10k) using `mpi4py.futures`, `ipyparallel`, `dask-mpi`, or `process-pool`.&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;requires-python&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;gt;=3.8&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;dynamic&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;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;maintainers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Bas Nijholt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;bas@nijho.lt&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;license&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;BSD-3&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;classifiers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Development Status :: 2 - Pre-Alpha&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Intended Audience :: Developers&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Intended Audience :: Science/Research&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;License :: OSI Approved :: BSD License&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Operating System :: OS Independent&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Programming Language :: Python&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Programming Language :: Python :: 3&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Programming Language :: Python :: 3.8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Programming Language :: Python :: 3.9&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Programming Language :: Python :: 3.10&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Programming Language :: Python :: 3.11&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Topic :: Scientific/Engineering&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Topic :: System :: Distributed Computing&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;adaptive &amp;gt;= 0.14.1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;cloudpickle&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;dill&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;ipyparallel&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;ipywidgets&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;itables&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;jinja2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;loky&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;numpy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;pandas&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;psutil&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;pyarrow&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;pyzmq&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;rich&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;structlog&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;tinydb&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;toolz&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;tqdm&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;versioningit&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;optional-dependencies&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;all&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;dask-mpi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;mpi4py&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;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-asyncio&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;coverage&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pytest-cov&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;docs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;myst-nb&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;sphinx_fontawesome&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;sphinx&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;furo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;myst-parser&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;emoji&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;homepage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://adaptive-scheduler.readthedocs.io/&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;documentation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://adaptive-scheduler.readthedocs.io/&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;repository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://github.com/basnijholt/adaptive-scheduler&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readme&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;content-type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;text/x-rst&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;README.rst&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scripts&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;adaptive-scheduler-launcher&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;adaptive_scheduler._server_support.launcher:main&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;versioningit&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;versioningit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onbuild&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;build-file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;adaptive_scheduler/_version.py&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;source-file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;adaptive_scheduler/_version.py&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setuptools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;packages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;include&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;adaptive_scheduler.*&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;adaptive_scheduler&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ini_options&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;addopts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; -vvv&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; --cov=adaptive_scheduler&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; --cov-report term&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; --cov-report html&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; --cov-report xml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; --cov-fail-under=35&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; --asyncio-mode=auto&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coverage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;report&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;exclude_lines&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;pragma: no cover&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;raise NotImplementedError&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;if TYPE_CHECKING:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;if __name__ == .__main__.:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;black&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;line_length&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;88&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ruff&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;line-length&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;target-version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;py38&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;select&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;ALL&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ignore&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;T20&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;# flake8-print&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;ANN101&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;# Missing type annotation for {name} in method&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;S101&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;# Use of assert detected&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;PD901&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;# df is a bad variable name. Be kinder to your future self.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;ANN401&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;# Dynamically typed expressions (typing.Any) are disallowed in {name}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;D402&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;# First line should not be the function&amp;#39;s signature&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;PLW0603&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;# Using the global statement to update `X` is discouraged&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;D401&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;# First line of docstring should be in imperative mood&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;SLF001&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;# Private member accessed&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ruff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;per-file-ignores&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;tests/*&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;SLF001&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;tests/test_examples.py&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;E501&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;.github/*&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;INP001&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ruff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mccabe&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;max-complexity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mypy&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;python_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;3.8&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;!-- OUTPUT:END --&gt;&#10;&lt;/details&gt;&#10;&lt;p&gt;(from &lt;a href="https://github.com/basnijholt/adaptive-scheduler/blob/main/pyproject.toml" target="_blank" rel="noopener"&gt;here&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;As seen in the example, the &lt;code&gt;pyproject.toml&lt;/code&gt; file includes details about the build system, project metadata, dependencies, optional dependencies, URLs, and configurations for various tools such as Versioningit, setuptools, pytest, coverage, Black, Ruff, and Mypy. By consolidating these configurations, you can maintain a more organized and maintainable project, making it easier to understand and update settings for different tools.&lt;/p&gt;&#10;&lt;p&gt;In summary, the &lt;code&gt;pyproject.toml&lt;/code&gt; file and adherence to PEP 621 provide a unified approach to configuring and setting up your Python project, making it easier to manage dependencies, metadata, and tool-specific settings. This streamlined process ultimately leads to a more efficient and maintainable development workflow.&lt;/p&gt;&#10;&lt;h2 id="-9-github-actions-ci-matrix-testing-on-multiple-platforms-and-auto-publishing-to-pypi"&gt;🌐 9. GitHub Actions CI Matrix: Testing on Multiple Platforms and Auto Publishing to PyPI &lt;a class="anchor" href="#-9-github-actions-ci-matrix-testing-on-multiple-platforms-and-auto-publishing-to-pypi" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://docs.github.com/en/actions/learn-github-actions" target="_blank" rel="noopener"&gt;GitHub Actions&lt;/a&gt; is an excellent tool for automating workflows and improving code quality through continuous integration (CI). By using a CI matrix, you can test your code on multiple platforms and Python versions, ensuring that your package remains compatible and stable across different environments.&lt;/p&gt;&#10;&lt;p&gt;To set up a &lt;code&gt;.github/workflows/nox.yaml&lt;/code&gt; file for GitHub Actions, you can use the following simplified example:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;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;nox&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pull_request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;main]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.platform }}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fail-fast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest, macos-latest, windows-latest]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;3.8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;3.9&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;3.10&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;3.11&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v2&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set up Python ${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-python@v2&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install dependencies&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;pip install nox&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&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;Test with nox&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&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="l"&gt;nox -e py${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This configuration file specifies the platforms and Python versions for testing, as well as the steps to set up the environment, install dependencies, and run tests using nox.&#10;With this simple configuration, you set up tests on MacOS, Windows, Linux, and multiple Python versions, ensuring that your package remains compatible across different environments.&lt;/p&gt;&#10;&lt;p&gt;Another essential part of the CI/CD process is deploying your Python package. You can use the &lt;code&gt;.github/workflows/publish-package.yaml&lt;/code&gt; file to automate the package deployment to PyPI whenever a new release is published:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;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;Upload Python Package&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;release&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;types&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;published]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v2&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set up Python&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-python@v2&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;3.x&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install dependencies&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; python -m pip install --upgrade pip&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; pip install setuptools wheel twine build&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&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;Build and publish&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;TWINE_USERNAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;__token__&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;TWINE_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.PYPI_TOKEN }}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; python -m build&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; twine upload dist/*&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This workflow sets up Python, installs necessary dependencies, builds the package, and publishes it to PyPI using Twine. The process integrates seamlessly with versioningit, ensuring that the published package has the correct version number based on the git history.&lt;/p&gt;&#10;&lt;p&gt;In summary, leveraging GitHub Actions for continuous integration and automated workflows enhances code quality and reliability, while also simplifying the development and deployment processes. By testing across various platforms and Python versions, and automating package deployment, you can be confident that your package is well-maintained and accessible to users.&lt;/p&gt;&#10;&lt;h2 id="-10-read-the-docs-sphinx-myst-nb-and-jupytext-notebooks-for-documentation"&gt;📚 10. Read the Docs, Sphinx, MyST-NB, and Jupytext notebooks for documentation &lt;a class="anchor" href="#-10-read-the-docs-sphinx-myst-nb-and-jupytext-notebooks-for-documentation" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Documentation is crucial for any software project, as it helps users understand how to use the package and provides guidance for contributors. &lt;a href="https://readthedocs.org/" target="_blank" rel="noopener"&gt;Read the Docs&lt;/a&gt;, &lt;a href="https://www.sphinx-doc.org/" target="_blank" rel="noopener"&gt;Sphinx&lt;/a&gt;, &lt;a href="https://myst-nb.readthedocs.io/en/latest/" target="_blank" rel="noopener"&gt;MyST-NB&lt;/a&gt;, and &lt;a href="https://jupytext.readthedocs.io/en/latest/" target="_blank" rel="noopener"&gt;Jupytext notebooks&lt;/a&gt; are powerful tools that, when combined, facilitate the creation and maintenance of high-quality documentation.&lt;/p&gt;&#10;&lt;p&gt;tl;dr: check out the &lt;a href="https://adaptive.readthedocs.io/" target="_blank" rel="noopener"&gt;Adaptive documentation&lt;/a&gt; for an example of how to use these tools to create a comprehensive documentation website.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://readthedocs.org/" target="_blank" rel="noopener"&gt;&lt;strong&gt;Read the Docs&lt;/strong&gt;&lt;/a&gt; is a documentation hosting platform that automatically builds, version controls, and hosts your documentation for free. It integrates well with Sphinx and GitHub, allowing your documentation to be updated whenever you push changes to your repository.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://www.sphinx-doc.org/" target="_blank" rel="noopener"&gt;&lt;strong&gt;Sphinx&lt;/strong&gt;&lt;/a&gt; is a documentation generator that can transform reStructuredText files, MyST files, or Jupyter notebooks into various output formats, such as HTML, LaTeX, or PDF. It is highly extensible, supporting custom themes and plugins, and can automatically generate API documentation from your Python code.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://myst-nb.readthedocs.io/en/latest/" target="_blank" rel="noopener"&gt;&lt;strong&gt;MyST-NB&lt;/strong&gt;&lt;/a&gt; is an extension to Sphinx that enables it to parse Jupyter notebooks and execute the code cells during the build process. It uses the MyST (short for Markedly Structured Text) markdown parser, which adds support for Sphinx roles and directives, making it a powerful alternative to reStructuredText.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://jupytext.readthedocs.io/en/latest/" target="_blank" rel="noopener"&gt;&lt;strong&gt;Jupytext notebooks&lt;/strong&gt;&lt;/a&gt; is a Jupyter extension that enables you to save Jupyter notebooks in Markdown format (or other text-based formats) instead of the default JSON format. By using Jupytext notebooks, you can store your code, explanations, and visualizations as plain text, which is easier to version control and can be seamlessly integrated with MyST-NB and Sphinx.&lt;/p&gt;&#10;&lt;p&gt;With &lt;a href="https://myst-nb.readthedocs.io/en/latest/" target="_blank" rel="noopener"&gt;MyST-NB&lt;/a&gt; and &lt;a href="https://jupytext.readthedocs.io/en/latest/" target="_blank" rel="noopener"&gt;Jupytext&lt;/a&gt;, you can write your documentation using Jupyter notebooks in Markdown format. This approach combines the benefits of Jupyter notebooks, such as interactive code execution and rich output, with the simplicity of Markdown and the powerful features of Sphinx. The result is a highly readable, user-friendly, and maintainable documentation that caters to both users and developers.&lt;/p&gt;&#10;&lt;p&gt;To get started with MyST-NB, you&amp;rsquo;ll need to install it using pip:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pip install myst-nb&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, configure your Sphinx project to use MyST-NB by adding the following lines to your &lt;code&gt;conf.py&lt;/code&gt; file:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;extensions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;myst_nb&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, Sphinx will be able to parse and execute your Jupyter notebooks during the documentation build process, integrating them seamlessly with your existing documentation.&lt;/p&gt;&#10;&lt;p&gt;In summary, using Read the Docs, Sphinx, MyST-NB, and Jupytext notebooks for your documentation enables you to create high-quality, interactive, and easily maintainable documentation that benefits your project&amp;rsquo;s users and contributors. This combination of tools ensures that your documentation remains up-to-date, easily navigable, and engaging, ultimately enhancing the overall experience for your project&amp;rsquo;s audience.&lt;/p&gt;&#10;&lt;p&gt;For a full example, see how I setup &lt;a href="https://adaptive.readthedocs.io/en/latest/" target="_blank" rel="noopener"&gt;Adaptive&amp;rsquo;s documentation&lt;/a&gt; using Sphinx, MyST-NB, and Jupytext notebooks. &lt;a href="https://github.com/python-adaptive/adaptive/blob/1b0f15e44235643731c56d335f6711c5584d4828/readthedocs.yml" target="_blank" rel="noopener"&gt;&lt;code&gt;readthedocs.yaml&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/python-adaptive/adaptive/blob/1b0f15e44235643731c56d335f6711c5584d4828/docs/source/conf.py" target="_blank" rel="noopener"&gt;&lt;code&gt;docs/source/conf.py&lt;/code&gt;&lt;/a&gt;, and here is an example notebook that is used in the &lt;a href="https://adaptive.readthedocs.io/en/latest/tutorial/tutorial.Learner1D.html" target="_blank" rel="noopener"&gt;documentation&lt;/a&gt;: &lt;a href="https://github.com/python-adaptive/adaptive/blob/main/docs/source/tutorial/tutorial.Learner1D.md" target="_blank" rel="noopener"&gt;&lt;code&gt;docs/source/tutorial/tutorial.Learner1D.md&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;h2 id="-11-conclusion"&gt;🎉 11. Conclusion &lt;a class="anchor" href="#-11-conclusion" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;In conclusion, incorporating these powerful tools into your Python development workflow can significantly enhance your productivity and code quality. By adopting &lt;a href="https://pre-commit.com/" target="_blank" rel="noopener"&gt;pre-commit&lt;/a&gt;, &lt;a href="https://black.readthedocs.io/en/stable/" target="_blank" rel="noopener"&gt;Black&lt;/a&gt;, &lt;a href="https://beta.ruff.rs/docs/rules/" target="_blank" rel="noopener"&gt;Ruff&lt;/a&gt;, &lt;a href="https://mypy.readthedocs.io/en/stable/" target="_blank" rel="noopener"&gt;Mypy&lt;/a&gt;, &lt;a href="https://versioningit.readthedocs.io/en/latest/" target="_blank" rel="noopener"&gt;Versioningit&lt;/a&gt;, &lt;a href="https://www.python.org/dev/peps/pep-0621/" target="_blank" rel="noopener"&gt;Pyproject.toml with PEP 621&lt;/a&gt;, &lt;a href="https://nox.thea.codes/en/stable/" target="_blank" rel="noopener"&gt;Nox&lt;/a&gt;, &lt;a href="https://docs.pytest.org/" target="_blank" rel="noopener"&gt;pytest&lt;/a&gt;, &lt;a href="https://docs.github.com/en/actions/learn-github-actions" target="_blank" rel="noopener"&gt;GitHub Actions CI matrix&lt;/a&gt;, and &lt;a href="https://readthedocs.org/" target="_blank" rel="noopener"&gt;Read the Docs&lt;/a&gt;, &lt;a href="https://www.sphinx-doc.org/" target="_blank" rel="noopener"&gt;Sphinx&lt;/a&gt;, &lt;a href="https://myst-nb.readthedocs.io/en/latest/" target="_blank" rel="noopener"&gt;MyST-NB&lt;/a&gt;, and &lt;a href="https://jupytext.readthedocs.io/en/latest/" target="_blank" rel="noopener"&gt;Jupytext notebooks&lt;/a&gt; for documentation, you create a streamlined, efficient, and reliable development environment.&lt;/p&gt;&#10;&lt;p&gt;These tools help maintain code consistency, catch issues early, automate package versioning, simplify testing, consolidate configurations, and create high-quality documentation. By integrating them into your projects, you can focus on writing great code while ensuring that your work remains robust, maintainable, and accessible to users and contributors alike.&lt;/p&gt;&#10;&lt;p&gt;Don&amp;rsquo;t hesitate to explore these tools and incorporate them into your Python development toolkit. They can transform your development experience, making it more enjoyable and efficient, ultimately leading to higher-quality projects and happier developers. Happy coding!&lt;/p&gt;&#10;</description></item></channel></rss>