<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>Chris Markiewicz</title>
<link>https://effigies.gitlab.io/</link>
<atom:link href="https://effigies.gitlab.io/index.xml" rel="self" type="application/rss+xml"/>
<description></description>
<generator>quarto-1.4.551</generator>
<lastBuildDate>Fri, 03 Jan 2025 00:00:00 GMT</lastBuildDate>
<item>
  <title>Running a git-annex forge on a Synology NAS</title>
  <dc:creator>Chris Markiewicz</dc:creator>
  <link>https://effigies.gitlab.io/posts/forgejo-aneksajo-synology/</link>
  <description><![CDATA[ 




<p>I recently purchased a <a href="https://www.synology.com/en-us/products/DS1522+">Synology DS1522+</a> for my house and have been transitioning my data to it. A significant amount of my data is in <a href="https://git-annex.branchable.com/">git-annex</a> repositories that are managed with <a href="https://gitolite.com/gitolite/">gitolite</a> on my server.</p>
<p>I used <a href="https://blog.datalad.org/posts/forgejo-aneksajo-podman-deployment/">Deploying and managing Forgejo-aneksajo with podman and systemd</a> from the <a href="https://blog.datalad.org/">DataLad blog</a> to guide my efforts, and this post is intended mainly as notes where I had to find alternate methods.</p>
<section id="docker" class="level2">
<h2 class="anchored" data-anchor-id="docker">Docker</h2>
<p>Synology ships a custom Linux distribution that does not come with a full-fledged package manager. It does, on the other hand, have a <a href="https://www.synology.com/en-us/dsm/packages/ContainerManager">Container Manager</a>, which is a <a href="https://www.docker.com/">Docker</a> interface. So the first adaptation is to use Docker instead of <a href="https://podman.io/">podman</a>, which means managing permissions.</p>
<section id="the-docker-group" class="level3">
<h3 class="anchored" data-anchor-id="the-docker-group">The docker group</h3>
<p>Synology has a web UI for creating groups, but you can use the command line:</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sudo</span> synogroup <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--add</span> docker</span>
<span id="cb1-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sudo</span> chown :docker /var/run/docker.sock</span></code></pre></div>
</section>
<section id="the-git-user" class="level3">
<h3 class="anchored" data-anchor-id="the-git-user">The git user</h3>
<p>I would like to use the convention of <code>git@&lt;host&gt;:&lt;org&gt;/&lt;repo&gt;</code> that is typical of git servers, but the Synology web UI does not permit you to create a <code>git</code> user. It does not restrict its CLI, however:</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sudo</span> synouser <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--add</span> git none <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"git user"</span> 0 noreply@nodomain none</span>
<span id="cb2-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sudo</span> passwd <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-ld</span> git </span></code></pre></div>
<p>Now, in order to run the SSH passthrough, the git user needs the docker group to be its primary group, not just a member. There is no equivalent to <code>usermod</code> available, even after installing <a href="https://www.busybox.net/">busybox</a>, so this needs to be edited manually.</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode default code-with-copy"><code class="sourceCode default"><span id="cb3-1">git:x:1000:65536:git user:/var/services/homes/git:/bin/sh</span></code></pre></div>
<p>The docker GID assigned was 65536, so that was set there. Also, Synology starts UIDs at 1024, but the container we’ll use uses 1000. For simplicity, I changed the UID here.</p>
</section>
<section id="the-docker-image" class="level3">
<h3 class="anchored" data-anchor-id="the-docker-image">The docker image</h3>
<p>For a first pass, I’m using the image published by Michael Hanke:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://effigies.gitlab.io/posts/forgejo-aneksajo-synology/container-registry.png" class="img-fluid figure-img"></p>
<figcaption>Screenshot of retrieving the mihanke/forgejo-aneksajo image</figcaption>
</figure>
</div>
<p>Very little configuration is needed, but it looks like this:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://effigies.gitlab.io/posts/forgejo-aneksajo-synology/container-config.png" class="img-fluid figure-img"></p>
<figcaption>Screenshot of container configuration</figcaption>
</figure>
</div>
</section>
</section>
<section id="ssh-passthrough" class="level2">
<h2 class="anchored" data-anchor-id="ssh-passthrough">SSH Passthrough</h2>
<p>Note above that we do not change the shell, which is a departure from the DataLad instructions. Synology appears to have custom PAM modules that prevent the use of arbitrary programs as shells, as opposed to the more standard <a href="https://linux.die.net/man/8/pam_shells">pam_shells (8)</a>.</p>
<p>Therefore we need to use <code>AuthorizedKeysCommand</code> in <a href="https://linux.die.net/man/5/sshd_config">sshd_config (5)</a> to do all of the work for us.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>/usr/local/bin/forgejo-keys</strong></pre>
</div>
<div class="sourceCode" id="cb4" data-filename="/usr/local/bin/forgejo-keys" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb4-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#!/bin/sh</span></span>
<span id="cb4-2"></span>
<span id="cb4-3"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">PROXY</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>/usr/local/bin/docker</span>
<span id="cb4-4"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">CONTAINER</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>forgejo</span>
<span id="cb4-5"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">KEYS_COMMAND</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/usr/local/bin/gitea keys -c /etc/gitea/app.ini"</span></span>
<span id="cb4-6"></span>
<span id="cb4-7"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$PROXY</span> exec <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$CONTAINER</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$KEYS_COMMAND</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$@</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb4-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">|</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sed</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-e</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'s@/@'"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">${PROXY}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> exec -i -e SSH_ORIGINAL_COMMAND </span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$CONTAINER</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> /@"</span></span></code></pre></div>
</div>
<p>This script passes arguments to <code>gitea keys</code> inside the container, which returns a command that can be run inside the container, for example:</p>
<div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode default code-with-copy"><code class="sourceCode default"><span id="cb5-1">command="/usr/local/bin/gitea --config=/etc/gitea/app.ini serv key-1",&lt;ssh-options&gt; ssh-ed25519 &lt;pubkey&gt;</span></code></pre></div>
<p>The <code>sed</code> script finds the first <code>/</code> and inserts a <code>docker exec</code> prefix, for example:</p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode default code-with-copy"><code class="sourceCode default"><span id="cb6-1">command="/usr/local/bin/docker exec -i -e SSH_ORGINAL_COMMAND forgejo /usr/local/bin/gitea --config=/etc/gitea/app.ini serv key-1",&lt;ssh-options&gt; ssh-ed25519 &lt;pubkey&gt;</span></code></pre></div>
<p>To ensure that this is usable by <code>sshd</code>, set the permissions correctly:</p>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb7-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sudo</span> chown root:root /usr/local/bin/forgejo-keys</span>
<span id="cb7-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sudo</span> chmod 755 /usr/local/bin/forgejo-keys</span></code></pre></div>
<p>Finally, update <code>sshd_config</code>:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>/etc/ssh/sshd_config</strong></pre>
</div>
<div class="sourceCode" id="cb8" data-filename="/etc/ssh/sshd_config" style="background: #f1f3f5;"><pre class="sourceCode default code-with-copy"><code class="sourceCode default"><span id="cb8-1">...</span>
<span id="cb8-2"></span>
<span id="cb8-3">Match User git</span>
<span id="cb8-4">    AuthorizedKeysCommandUser git</span>
<span id="cb8-5">    AuthorizedKeysCommand /usr/local/bin/forgejo-keys -e git -u %u -t %t -k %k</span></code></pre></div>
</div>
</section>
<section id="reverse-proxy" class="level2">
<h2 class="anchored" data-anchor-id="reverse-proxy">Reverse proxy</h2>
<p>The only remaining thing is to set up Synology’s reverse proxy through their Control Panel:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://effigies.gitlab.io/posts/forgejo-aneksajo-synology/reverse-proxy.png" class="img-fluid figure-img"></p>
<figcaption>Screenshot of reverse proxy configuration</figcaption>
</figure>
</div>
<p>A little bit more configuration is needed to get a <a href="https://letsencrypt.org">LetsEncrypt</a> certificate working. If I flesh out this post into a full how-to, I will include that as well.</p>


</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by/4.0/">CC BY 4.0</a></div></div></section></div> ]]></description>
  <category>git</category>
  <category>git-annex</category>
  <category>datalad</category>
  <category>NAS</category>
  <guid>https://effigies.gitlab.io/posts/forgejo-aneksajo-synology/</guid>
  <pubDate>Fri, 03 Jan 2025 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Content-triggered make rules</title>
  <dc:creator>Chris Markiewicz</dc:creator>
  <link>https://effigies.gitlab.io/posts/content-triggered-make-rules/</link>
  <description><![CDATA[ 




<p>While working on a project where some content needs to be retrieved from an API, it occurred to me that it would be preferable to make a small request that would indicate whether a larger request needs to be made.</p>
<p>The basic pattern turns out to be as follows:</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode makefile code-with-copy"><code class="sourceCode makefile"><span id="cb1-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Cheap fetch that will indicate whether the main target will have changed</span></span>
<span id="cb1-2"><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">file.stamp:</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;"> FORCE</span></span>
<span id="cb1-3"><span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">    </span>curl <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">URL1</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">)</span> &gt; file.stamp</span>
<span id="cb1-4"></span>
<span id="cb1-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Main target, could trigger additional work locally or remotely</span></span>
<span id="cb1-6"><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">file:</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;"> file.stamp.md5</span></span>
<span id="cb1-7"><span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">    </span>curl <span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">URL2</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">)</span> &gt; file</span>
<span id="cb1-8"></span>
<span id="cb1-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Generate an .md5 file, but only write if the hash changes</span></span>
<span id="cb1-10"><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">%.md5:</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;"> %</span></span>
<span id="cb1-11"><span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">    </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$(</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$(</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">filter-out</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$(</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">shell</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> cat </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$@</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> 2&gt;/dev/null</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">)</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$(</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">shell</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> md5sum </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$*))</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> md5sum </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$*</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> &gt; </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$@)</span></span>
<span id="cb1-12"></span>
<span id="cb1-13"><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">FORCE:</span></span></code></pre></div>
<p>Though I’m using API calls for an example, nothing in this is really specific to URL fetches.</p>
<p>Here our main target is some <code>file</code> that is expensive to generate, or updating it might cascade to the entire build. We will know if we need to rerun <code>file:</code> if some <code>file.stamp</code> changes, but we need to perform some work to see if it needs to change. Therefore we depend on a phony rule <code>FORCE</code> to ensure <code>file.stamp</code> is always run. <code>file.stamp.md5</code> is then produced by the third rule, but if the checksum is unchanged the file is not written. <code>file:</code> can then depend on <code>file.stamp.md5</code> and not <code>file.stamp</code> to avoid doing unnecessary work.</p>
<p>To make it concrete, my specific use case is retrieving <a href="https://zenodo.org">Zenodo</a> records where <a href="https://orcid.org/0000-0002-6533-164X">my ORCID</a> is listed as a creator. The Zenodo API has a curious bug where asking for a larger number of records than are responsive will simply retrieve additional records, so I need to run a short query to find out how many there are first. While I am at it, I can check what the latest modification time was</p>
<p>Here is a simplified version of my actual <code>Makefile</code>:</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode makefile code-with-copy"><code class="sourceCode makefile"><span id="cb2-1"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">ORCID</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">0000-0002-6533-164X</span></span>
<span id="cb2-2"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">BASEQUERY</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"https://zenodo.org/api/records/?q=creators.orcid:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">ORCID</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">)</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb2-3"></span>
<span id="cb2-4"><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">zenodo.bib:</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;"> zenodo.stamp.md5</span></span>
<span id="cb2-5"><span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">    </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$(</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">eval</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> SIZE=</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$(</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">shell</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> jq .[1] zenodo.stamp </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">))</span></span>
<span id="cb2-6">    curl -sSL -H <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Accept: application/x-bibtex'</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">BASEQUERY</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">)</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">&amp;size=</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">SIZE</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">)</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span> -o zenodo.bib</span>
<span id="cb2-7"></span>
<span id="cb2-8"><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">zenodo.stamp:</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;"> FORCE</span></span>
<span id="cb2-9"><span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">    </span>curl -sSL <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">BASEQUERY</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">)</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">&amp;size=1&amp;sort=-publication_date"</span> | \</span>
<span id="cb2-10">    jq <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"[.hits.hits[0].updated, .hits.total]"</span> &gt; zenodo.stamp</span>
<span id="cb2-11"></span>
<span id="cb2-12"><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">%.md5:</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;"> %</span></span>
<span id="cb2-13"><span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">    </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$(</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$(</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">filter-out</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$(</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">shell</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> cat </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$@</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> 2&gt;/dev/null</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">)</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$(</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">shell</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> md5sum </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$*))</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> md5sum </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$*</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> &gt; </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">$@)</span></span>
<span id="cb2-14"></span>
<span id="cb2-15"><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">FORCE:</span></span></code></pre></div>
<p>Here it is working:</p>
<pre class="console"><code>$ make zenodo.bib
curl -sSL ""https://zenodo.org/api/records/?q=creators.orcid:0000-0002-6533-164X"&amp;size=1&amp;sort=-publication_date" | \
jq "[.hits.hits[0].updated, .hits.total]" &gt; zenodo.stamp
md5sum zenodo.stamp &gt; zenodo.stamp.md5
curl -sSL -H 'Accept: application/x-bibtex' ""https://zenodo.org/api/records/?q=creators.orcid:0000-0002-6533-164X"&amp;size=25" -o zenodo.bib
$ cat zenodo.stamp
[
  "2023-02-19T02:26:51.701065+00:00",
  25
]
~/tmp 
$ cat zenodo.stamp.md5 
b851094530e336c4df14d0807a28c596  zenodo.stamp</code></pre>
<p>Re-running:</p>
<pre class="console"><code>$ make zenodo.bib     
curl -sSL ""https://zenodo.org/api/records/?q=creators.orcid:0000-0002-6533-164X"&amp;size=1&amp;sort=-publication_date" | \
jq "[.hits.hits[0].updated, .hits.total]" &gt; zenodo.stamp
$ ls -l zenodo.stamp*
-rw-rw-r-- 1 chris chris 47 Feb 26 22:00 zenodo.stamp
-rw-rw-r-- 1 chris chris 47 Feb 26 21:59 zenodo.stamp.md5</code></pre>
<p>Note that the <code>zenodo.stamp</code> file is newer than <code>zenodo.stamp.md5</code>.</p>
<section id="references" class="level2">
<h2 class="anchored" data-anchor-id="references">References</h2>
<ul>
<li><a href="https://www.cmcrossroads.com/article/rebuilding-when-files-checksum-changes">Rebuilding When a File’s Checksum Changes</a> (which almost worked…)</li>
</ul>


</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by/4.0/">CC BY 4.0</a></div></div></section></div> ]]></description>
  <category>code</category>
  <category>Makefile</category>
  <category>hacks</category>
  <guid>https://effigies.gitlab.io/posts/content-triggered-make-rules/</guid>
  <pubDate>Sun, 26 Feb 2023 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Contemporary Python Packaging (2023)</title>
  <dc:creator>Chris Markiewicz</dc:creator>
  <link>https://effigies.gitlab.io/posts/python-packaging-2023/</link>
  <description><![CDATA[ 




<p>This document lays out a set of Python packaging practices. I don’t claim they are <em>best</em> practices, but they fit my needs, and might fit yours.</p>
<section id="validity" class="level2">
<h2 class="anchored" data-anchor-id="validity">Validity</h2>
<p>This was written in January 2023, superseding previous posts from <a href="../python-packaging-2020">2020</a> and <a href="../python-packaging-2019">2019</a>.</p>
<p>As of this writing, Python 3.7 is approaching its end-of-life and many packages have already set a minimum version of Python 3.8. This document should be superseded or disregarded no later than the Python 3.9 end-of-life. If you cite this as a justification for your behavior, please stop doing so at that time.</p>
</section>
<section id="summary" class="level2">
<h2 class="anchored" data-anchor-id="summary">Summary</h2>
<ul>
<li>Describe all of your project metadata in <a href="https://packaging.python.org/en/latest/specifications/declaring-project-metadata"><code>pyproject.toml</code></a></li>
<li>Use <a href="https://hatch.pypa.io">hatchling</a> to build sdists and wheels</li>
<li>Use <a href="https://github.com/ofek/hatch-vcs">hatch-vcs</a> to extract the version from git and, optionally, write into a module</li>
<li>Test your packages with a build-test-deploy workflow in CI</li>
</ul>
<p>A <a href="https://www.cookiecutter.io/">Cookiecutter</a> template implementing these suggestions can be found at <a href="https://github.com/effigies/cookiecutter-packaging" class="uri">https://github.com/effigies/cookiecutter-packaging</a>.</p>
<section id="changes-from-previous-revision" class="level3">
<h3 class="anchored" data-anchor-id="changes-from-previous-revision">Changes from previous revision</h3>
<ul>
<li>Drop setuptools and versioneer</li>
<li>Reduce focus on support for legacy versions of pip or build tools</li>
<li>Add section on continuous integration (CI) for testing packages</li>
</ul>
</section>
<section id="likely-future-changes" class="level3">
<h3 class="anchored" data-anchor-id="likely-future-changes">Likely future changes</h3>
<ul>
<li>Adjust to use <code>src/</code> layout instead of flat</li>
<li>Discussion of namespace packages</li>
<li>Discussion of versioning multiple packages within a <a href="https://en.wikipedia.org/wiki/Monorepo">monorepo</a></li>
<li>Testing environments using hatch or tox</li>
</ul>
</section>
</section>
<section id="perspective" class="level2">
<h2 class="anchored" data-anchor-id="perspective">Perspective</h2>
<p>My position in the Python ecosystem will color my perspective and approach to packaging, and, presumably, how much weight you give what I think.</p>
<p>I work primarily on neuroimaging packages. I am currently the lead maintainer of <a href="https://github.com/nipy/nibabel">NiBabel</a> and keep the lights on at <a href="https://github.com/nipy/nipype">Nipype 1.x</a> while the transition to <a href="https://github.com/nipype/pydra">Pydra</a> progresses. I also maintain <a href="https://github.com/bids-standard/pybids">PyBIDS</a>, <a href="https://github.com/poldracklab/fmriprep">fMRIPrep</a> and a few other packages closely related to the these.</p>
<p>These packages have different requirements. A couple worth mentioning are</p>
<ol type="1">
<li>The <a href="https://nipy.org">nipy</a> packages have historically included git hash references and version information to allow users to provide detailed debugging information. Preserving this while updating the infrastructure has taken effort.</li>
<li>Pydra aims to keep a similar import infrastructure to nipype, so that <code>from nipype.interfaces import fsl</code> becomes <code>from pydra.tasks import fsl</code>, while separating out the tasks into task packages. <code>pydra.tasks.fsl</code> is provided by a <code>pydra-fsl</code> package.</li>
</ol>
<p>A couple years ago, I became a maintainer of <a href="https://github.com/python-versioneer/python-versioneer/">versioneer</a>, which until a few years ago was <em>the</em> way in Python to extract versions from Git history. As a result, I’ve become a bit more familiar with the internals and development trajectory of setuptools.</p>
<p>On the whole, I believe the Python packaging ecosystem is moving in a positive direction. The changes in this document over previous versions reflect the evolution of the standards and tooling more than in my philosophy. In particular, the standardization of declarative package metadata and editable installs mean that the choice of build backends is now less fraught, and we are free to choose the simplest that gets the job done.</p>
</section>
<section id="desiderata" class="level2">
<h2 class="anchored" data-anchor-id="desiderata">Desiderata</h2>
<p>Motivating my recommendations are a few desiderata, in rough order of importance:</p>
<ol type="1">
<li>Installation should work, from source, on fairly old systems. Debian Stable (11; “bullseye”) is my touchstone here.</li>
<li>Prefer declarative syntax, and limit dynamic metadata, as much as possible.</li>
<li>Enable revision-based versions, with minimal opportunity for error.</li>
<li>Limit custom code to absolute minimum. (Partially redundant with limiting dynamic metadata.)</li>
</ol>
<p>To operationalize (1), the following approaches should all install correctly:</p>
<ul>
<li><code>pip install .</code></li>
<li><code>python -m build &amp;&amp; pip install dist/*.tar.gz</code></li>
<li><code>python -m build &amp;&amp; pip install dist/*.whl</code></li>
</ul>
<p>And development/editable mode should work:</p>
<ul>
<li><code>pip install -e .</code></li>
</ul>
<p>To operationalize (3), all of the above should produce an install with the same version string, and setting the version should be done from a version control tag if possible. Assuming a <code>git</code> repository, the following should also work:</p>
<ul>
<li><code>git archive -o archive.tar.gz $TAG &amp;&amp; pip install archive.tar.gz</code></li>
</ul>
</section>
<section id="recommendations" class="level2">
<h2 class="anchored" data-anchor-id="recommendations">Recommendations</h2>
<p>I recommend using <a href="https://hatch.pypa.io">hatchling</a> and <a href="https://github.com/ofek/hatch-vcs">hatch-vcs</a>.</p>
<section id="pyproject.toml" class="level3">
<h3 class="anchored" data-anchor-id="pyproject.toml">pyproject.toml</h3>
<p>Create a <code>pyproject.toml</code> file. This contains <code>[build-system]</code>, <code>[project]</code>, and <code>[tool]</code> tables.</p>
<section id="build-system" class="level4">
<h4 class="anchored" data-anchor-id="build-system">Build system</h4>
<p>Python has the notion of build “frontends” and “backends”. The frontend is the command you call, such as <code>pip</code> or <code>python -m build</code>, while the backend is the tool these tools call to turn your source code into a built package or installed module.</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode toml code-with-copy"><code class="sourceCode toml"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">[build-system]</span></span>
<span id="cb1-2"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">requires</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hatchling"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hatch-vcs"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb1-3"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">build-backend</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hatchling.build"</span></span></code></pre></div>
<p><code>hatchling</code> is the build backend used by <a href="https://hatch.pypa.io">hatch</a>, which is a larger project management tool. <a href="https://github.com/ofek/hatch-vcs">hatch-vcs</a> wraps <a href="https://github.com/pypa/setuptools_scm">setuptools_scm</a> to retrieve the version from VCS and write a version file to disk.</p>
</section>
<section id="project-metadata" class="level4">
<h4 class="anchored" data-anchor-id="project-metadata">Project metadata</h4>
<p>Most packaging metadata can be set declaratively in the <code>[project]</code> table. See <a href="https://packaging.python.org/en/latest/specifications/declaring-project-metadata">Declaring project metadata</a> for the full specification of this table.</p>
<p>The following skeleton can be used as a model.</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode toml code-with-copy"><code class="sourceCode toml"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">[project]</span></span>
<span id="cb2-2"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">name</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"project-name"</span></span>
<span id="cb2-3"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">description</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A package"</span></span>
<span id="cb2-4"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">readme</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"README.md"</span></span>
<span id="cb2-5"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">requires-python</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&gt;=3.8"</span></span>
<span id="cb2-6"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">license</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{ </span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">file</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"LICENSE"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"> }</span></span>
<span id="cb2-7"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">authors</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span></span>
<span id="cb2-8">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{ </span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">name</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">, </span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">email</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"your@email.tld"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"> },</span></span>
<span id="cb2-9"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb2-10"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">classifiers</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span></span>
<span id="cb2-11">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Programming Language :: Python :: 3"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb2-12"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb2-13"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">dependencies</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[]</span></span>
<span id="cb2-14"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">dynamic</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"version"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb2-15"></span>
<span id="cb2-16"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">[project.urls]</span></span>
<span id="cb2-17"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"Homepage"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"https://github.com/your/package"</span></span></code></pre></div>
<p>Note the <code>dynamic = ["version"]</code> field, which is required to use <code>hatch-vcs</code> to determine the version.</p>
<p>I strongly recommend including the <code>requires-python</code> field. This will prevent <code>pip</code> from attempting to install on incompatible systems. When you drop 3.8 - or any other versions - update the <code>requires-python</code> to avoid breaking downstream tools that still install on unsupported versions.</p>
</section>
<section id="tool-configuration" class="level4">
<h4 class="anchored" data-anchor-id="tool-configuration">Tool configuration</h4>
<section id="versioning" class="level5">
<h5 class="anchored" data-anchor-id="versioning">Versioning</h5>
<p>All that is needed to set the version from git now is:</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode toml code-with-copy"><code class="sourceCode toml"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">[tool.hatch.version]</span></span>
<span id="cb3-2"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">source</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"vcs"</span></span></code></pre></div>
<p>If you would like to make the version accessible from python, for example:</p>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> project_name</span>
<span id="cb4-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(project_name.__version__)</span></code></pre></div>
<p>Then you need to write to file. Add the following to <code>pyproject.toml</code>:</p>
<div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode toml code-with-copy"><code class="sourceCode toml"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">[tool.hatch.build.hooks.vcs]</span></span>
<span id="cb5-2"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">version-file</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"project_name/_version.py"</span></span></code></pre></div>
<p>And ensure that the version is imported into <code>__init__.py</code>:</p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">try</span>:</span>
<span id="cb6-2">    <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> ._version <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> version <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> __version__</span>
<span id="cb6-3"><span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">except</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">ImportError</span>:</span>
<span id="cb6-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">pass</span></span></code></pre></div>
<p>Note that this will be modified every time you <code>build</code> or <code>pip install</code> the package, so you should add <code>_version.py</code> to your <code>.gitignore</code>.</p>
<p>To allow <code>git-archive</code> to inject version information that will be picked up, create <code>.git-archival.txt</code>:</p>
<pre class="plain"><code>node: $Format:%H$
node-date: $Format:%cI$
describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$
ref-names: $Format:%D$</code></pre>
<p>And create/add the following line to <code>.gitattributes</code>:</p>
<pre><code>.git_archival.txt  export-subst</code></pre>
</section>
<section id="package-data" class="level5">
<h5 class="anchored" data-anchor-id="package-data">Package data</h5>
<p>Hatchling defaults to including everything in your VCS into your sdist and everything inside the package directories of your sdist into your wheels. You can customize each of these individually:</p>
<div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode toml code-with-copy"><code class="sourceCode toml"><span id="cb9-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">[tool.hatch.build.targets.sdist]</span></span>
<span id="cb9-2"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">exclude</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">".git_archival.txt"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb9-3"></span>
<span id="cb9-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">[tool.hatch.build.targets.wheel]</span></span>
<span id="cb9-5"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">packages</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"project_name"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb9-6"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">exclude</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span></span>
<span id="cb9-7">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"project_name/data/test_data"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-8"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span></code></pre></div>
<p>Exclude <code>.git_archival.txt</code> so that it appears in git archives, but not sdists, which already have package metadata and <code>version-file</code>s created.</p>
<p>By specifying <code>packages</code>, you can have additional directories with scripts for managing your repository without confusing <code>hatch</code>.</p>
<p>In passing, you might consider removing large test data from your wheel while leaving it in your sdist. Linux distributions will often prefer sdist over a repository as a canonical source for a version, so including all package data there is a good idea. It’s becoming common to have many virtual environments installed on a system, so keeping the footprint of installed wheels to a minimum is a good practice.</p>
</section>
</section>
</section>
</section>
<section id="notes-on-new-build-systems-and-legacy-operating-systems" class="level2">
<h2 class="anchored" data-anchor-id="notes-on-new-build-systems-and-legacy-operating-systems">Notes on new build systems and legacy operating systems</h2>
<p>The standardization of build frontends and backends has dramatically reduced my concerns about backwards compatibility. As long as a user is able to upgrade pip with <code>pip install --upgrade --user pip</code>, essentially any build system becomes available. The main place where this could become problematic is on systems that can’t fetch from PyPI due to network access restrictions. In this case, distributing pre-built wheels that do not need any backend processing is probably cleanest.</p>
<section id="why-not-setuptools" class="level3">
<h3 class="anchored" data-anchor-id="why-not-setuptools">Why not setuptools?</h3>
<p>In this post I recommend moving away from setuptools. On the one hand, this is because I think the new approaches are better and cleaner. On the other hand, as the various build systems converge on supporting common standards, setuptools is becoming less stable.</p>
<p>Setuptools seems to be placed in an impossible situation, to simultaneously support decades of legacy package specifications as well as a collection of new standards that have resulted from dissatisfaction with how things have been done for decades. As a result, the churn is very high at the moment. Major versions are bumped multiple times a year, with support for new standards coming alongside deprecations or breakages of unadvertised but longstanding behavior.</p>
<p>So it’s difficult to incrementally adopt new standards while relying on the setuptools-specific bits to continue working as always. At a certain point it became less work to learn a new build backend than to keep on with setuptools. The good news is that there is a choice of backends, and 90% of the configuration should be the same, no matter which you choose.</p>
<p>The next jump, if it comes, should be less painful.</p>
</section>
</section>
<section id="continuous-integration" class="level2">
<h2 class="anchored" data-anchor-id="continuous-integration">Continuous Integration</h2>
<p>Whether you use the recommended approach or not, it’s worth checking that your packages build correctly. In addition to users of your packages, you may have third-party packagers that will prepare your package to be installed via <a href="https://docs.conda.io">conda</a> or a Linux distribution-specific package manager. Testing your packages outside your repository reduces the chances of distributing broken packages.</p>
<p>The following is a minimal GitHub Actions specification for testing packages, but it should be easily translatable to other continuous integration services.</p>
<div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml"><span id="cb10-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">on</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">push</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-3"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">branches</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-4"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> master</span></span>
<span id="cb10-5"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> maint/*</span></span>
<span id="cb10-6"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tags</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-7"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"*"</span></span>
<span id="cb10-8"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">pull_request</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-9"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">branches</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-10"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> master</span></span>
<span id="cb10-11"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> maint/*</span></span>
<span id="cb10-12"></span>
<span id="cb10-13"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">defaults</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-14"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">run</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-15"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">shell</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> bash</span></span>
<span id="cb10-16"></span>
<span id="cb10-17"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">jobs</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-18"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">build</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-19"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">runs-on</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> ubuntu-latest</span></span>
<span id="cb10-20"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">steps</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-21"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">uses</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> actions/checkout@v3</span></span>
<span id="cb10-22"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">with</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-23"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">fetch-depth</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb10-24"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">uses</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> actions/setup-python@v4</span></span>
<span id="cb10-25"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">with</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-26"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">python-version</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span></span>
<span id="cb10-27"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">run</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> pip install --upgrade build twine</span></span>
<span id="cb10-28"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Build sdist and wheel</span></span>
<span id="cb10-29"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">run</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> python -m build</span></span>
<span id="cb10-30"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">run</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> twine check dist/*</span></span>
<span id="cb10-31"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Upload sdist and wheel artifacts</span></span>
<span id="cb10-32"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">uses</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> actions/upload-artifact@v3</span></span>
<span id="cb10-33"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">with</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-34"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> dist</span></span>
<span id="cb10-35"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">path</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> dist/</span></span>
<span id="cb10-36"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Build git archive</span></span>
<span id="cb10-37"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">run</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> mkdir archive &amp;&amp; git archive -v -o archive/archive.tgz HEAD</span></span>
<span id="cb10-38"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Upload git archive artifact</span></span>
<span id="cb10-39"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">uses</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> actions/upload-artifact@v3</span></span>
<span id="cb10-40"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">with</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-41"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> archive</span></span>
<span id="cb10-42"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">path</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> archive/</span></span>
<span id="cb10-43"></span>
<span id="cb10-44"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">test-package</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-45"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">runs-on</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> ubuntu-latest</span></span>
<span id="cb10-46"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">needs</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">build</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb10-47"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">strategy</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-48"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">matrix</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-49"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">package</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'wheel'</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'sdist'</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'archive'</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb10-50"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">steps</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-51"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Download sdist and wheel artifacts</span></span>
<span id="cb10-52"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">if</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> matrix.package != 'archive'</span></span>
<span id="cb10-53"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">uses</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> actions/download-artifact@v3</span></span>
<span id="cb10-54"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">with</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-55"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> dist</span></span>
<span id="cb10-56"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">path</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> dist/</span></span>
<span id="cb10-57"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Download git archive artifact</span></span>
<span id="cb10-58"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">if</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> matrix.package == 'archive'</span></span>
<span id="cb10-59"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">uses</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> actions/download-artifact@v3</span></span>
<span id="cb10-60"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">with</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-61"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> archive</span></span>
<span id="cb10-62"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">path</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> archive/</span></span>
<span id="cb10-63"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">uses</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> actions/setup-python@v4</span></span>
<span id="cb10-64"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">with</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-65"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">python-version</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span></span>
<span id="cb10-66"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Display Python version</span></span>
<span id="cb10-67"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">run</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> python -c "import sys; print(sys.version)"</span></span>
<span id="cb10-68"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Update pip</span></span>
<span id="cb10-69"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">run</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> pip install --upgrade pip</span></span>
<span id="cb10-70"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Install wheel</span></span>
<span id="cb10-71"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">if</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> matrix.package == 'wheel'</span></span>
<span id="cb10-72"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">run</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> pip install dist/*.whl</span></span>
<span id="cb10-73"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Install sdist</span></span>
<span id="cb10-74"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">if</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> matrix.package == 'sdist'</span></span>
<span id="cb10-75"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">run</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> pip install dist/*.tar.gz</span></span>
<span id="cb10-76"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Install archive</span></span>
<span id="cb10-77"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">if</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> matrix.package == 'archive'</span></span>
<span id="cb10-78"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">run</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> pip install archive/archive.tgz</span></span>
<span id="cb10-79"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Install test extras</span></span>
<span id="cb10-80"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">run</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> pip install project-name[test]</span></span>
<span id="cb10-81"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Run tests</span></span>
<span id="cb10-82"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">run</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> pytest --doctest-modules -v --pyargs project_name</span></span>
<span id="cb10-83"></span>
<span id="cb10-84"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">publish</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-85"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">runs-on</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> ubuntu-latest</span></span>
<span id="cb10-86"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">needs</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">test-package</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb10-87"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">if</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> github.event_name == 'push' &amp;&amp; startsWith(github.ref, 'refs/tags/')</span></span>
<span id="cb10-88"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">steps</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-89"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">uses</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> actions/download-artifact@v3</span></span>
<span id="cb10-90"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">with</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-91"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> dist</span></span>
<span id="cb10-92"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">path</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> dist/</span></span>
<span id="cb10-93"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">uses</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> pypa/gh-action-pypi-publish@release/v1</span></span>
<span id="cb10-94"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">with</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb10-95"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">user</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> __token__</span></span>
<span id="cb10-96"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">password</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> ${{ secrets.PYPI_API_TOKEN }}</span></span></code></pre></div>
<p>(Apologies for the lack of newlines. They seem to be removed by the syntax highlighter.)</p>
<p>To use the <code>publish</code> step, you will need to add a <a href="https://pypi.org/help/#apitoken">PyPI token</a> to your <a href="https://docs.github.com/en/actions/security-guides/encrypted-secrets">encrypted secrets</a>.</p>
</section>
<section id="references" class="level2">
<h2 class="anchored" data-anchor-id="references">References</h2>
<ul>
<li><a href="https://packaging.python.org/en/latest/specifications/declaring-project-metadata">Python Packaging User Guide - Declaring project metadata</a></li>
</ul>
<section id="peps" class="level3">
<h3 class="anchored" data-anchor-id="peps">PEPs</h3>
<ul>
<li><a href="https://www.python.org/dev/peps/pep-0440/">440</a></li>
<li><a href="https://www.python.org/dev/peps/pep-0508/">508</a></li>
<li><a href="https://www.python.org/dev/peps/pep-0517/">517</a></li>
<li><a href="https://www.python.org/dev/peps/pep-0518/">518</a></li>
<li><a href="https://www.python.org/dev/peps/pep-0621/">621</a></li>
</ul>


</section>
</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by/4.0/">CC BY 4.0</a></div></div></section></div> ]]></description>
  <category>python</category>
  <category>code</category>
  <category>packaging</category>
  <guid>https://effigies.gitlab.io/posts/python-packaging-2023/</guid>
  <pubDate>Mon, 16 Jan 2023 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Blogging</title>
  <dc:creator>Chris Markiewicz</dc:creator>
  <link>https://effigies.gitlab.io/posts/first-post/</link>
  <description><![CDATA[ 




<p>I’m trying out <a href="https://quarto.org">Quarto</a> to collect published materials.</p>
<p>The majority of the content I’ve produced in the last several years has been on my <a href="https://github.com/effigies/">GitHub</a>, including some talks. I’m curious to try transitioning them over to Quarto-based talks, which should allow a combination of RevealJS and Jupytext.</p>
<p>The occasional <a href="https://gist.github.com/effigies">gist</a> I’ve posted has been something along the lines of a blog post. I’ll aim to post here instead. I’ve back-filled a couple posts I’ve made on Python packaging. I intend to update these recommendations soon, which is what inspired me to create this site.</p>



<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by/4.0/">CC BY 4.0</a></div></div></section></div> ]]></description>
  <category>news</category>
  <guid>https://effigies.gitlab.io/posts/first-post/</guid>
  <pubDate>Thu, 17 Nov 2022 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Contemporary Python Packaging (2020)</title>
  <dc:creator>Chris Markiewicz</dc:creator>
  <link>https://effigies.gitlab.io/posts/python-packaging-2020/</link>
  <description><![CDATA[ 




<p>This document lays out a set of Python packaging practices. I don’t claim they are <em>best</em> practices, but they fit my needs, and might fit yours.</p>
<p>This was previously published as a GitHub <a href="https://gist.github.com/effigies/9bbb424535d6a1d838d6325191c0a736">gist</a>.</p>
<section id="validity" class="level2">
<h2 class="anchored" data-anchor-id="validity">Validity</h2>
<p>This was written in July 2020, superseding <a href="https://gist.github.com/effigies/c9f4194034ee218bab1668bfd7851cfc">this gist</a> from 2019.</p>
<p>As of this writing, Python 3.5 is approaching its end-of-life and many packages have already set a minimum version of Python 3.6. This document should be superseded or disregarded no later than the Python 3.7 end-of-life. If you cite this as a justification for your behavior, please stop doing so at that time.</p>
</section>
<section id="summary" class="level2">
<h2 class="anchored" data-anchor-id="summary">Summary</h2>
<ul>
<li>For versioning, use <a href="https://github.com/warner/python-versioneer">versioneer</a></li>
<li>For describing your build requirements, use <a href="https://www.python.org/dev/peps/pep-0518/#build-system-table"><code>pyproject.toml</code></a></li>
<li>For all static (and some dynamic-ish) metadata, use <a href="https://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files"><code>setup.cfg</code></a></li>
<li>A very small <code>setup.py</code> for dynamic metadata and to tie everything together</li>
<li>Use <code>MANIFEST.in</code> for files that cannot otherwise be included in the sdist</li>
</ul>
<section id="changes-from-previous-revision" class="level3">
<h3 class="anchored" data-anchor-id="changes-from-previous-revision">Changes from previous revision</h3>
<ul>
<li>Updates to how to include data files in packages.
<ul>
<li>Reduce scope of <code>MANIFEST.in</code></li>
<li>Do <em>not</em> use <code>include_package_data = True</code> in <code>setup.cfg</code></li>
</ul></li>
<li>Dropped <code>tests_require</code> from <code>setup.cfg</code> - <code>python setup.py test</code> was deprecated in setuptools 41.5</li>
<li>Make <code>pyproject.toml</code> more clearly optional; some use cases are made harder by it.</li>
</ul>
</section>
<section id="likely-future-changes" class="level3">
<h3 class="anchored" data-anchor-id="likely-future-changes">Likely future changes</h3>
<ul>
<li>Versioneer is starting to show its age, and has not accepted a change since 2017. There are alternatives that are worth considering, but I haven’t evaluated them yet.</li>
</ul>
</section>
</section>
<section id="perspective" class="level2">
<h2 class="anchored" data-anchor-id="perspective">Perspective</h2>
<p>My position in the Python ecosystem will color my perspective and approach to packaging, and, presumably, how much weight you give what I think.</p>
<p>I am most active with the <a href="https://nipy.org">NIPY</a> collection of projects, generally related to neuroimaging and neuroscience. I am currently the lead maintainer of <a href="https://github.com/nipy/nibabel">NiBabel</a> and do a reasonable amount of maintenance work for <a href="https://github.com/nipy/nipype">Nipype</a>, <a href="https://github.com/bids-standard/pybids">PyBIDS</a>, <a href="https://github.com/poldracklab/fmriprep">fMRIPrep</a> and a few other packages closely related to the aforementioned.</p>
<p>I am not an active developer in <a href="https://github.com/python/cpython">CPython</a>, <a href="https://github.com/pypa/">PyPA</a> or any packaging-related tools. I have not followed deep arguments, but have relied mostly on PEPs, documentation and sporadic searches to identify the current state of the art.</p>
<p>So my perspective is less concerned with what packaging should become, and more with what works today and where things appear to be heading, as I look to prepare or update packaging infrastructure for several tools. Infrastructure I hope to stop thinking about so much.</p>
</section>
<section id="desiderata" class="level2">
<h2 class="anchored" data-anchor-id="desiderata">Desiderata</h2>
<p>Motivating my recommendations are a few desiderata, in rough order of importance:</p>
<ol type="1">
<li>Installation should work, from source, on fairly old systems. Debian Stable (10; “buster”) is my touchstone here.</li>
<li>Prefer declarative syntax, and limit dynamic metadata, as much as possible.</li>
<li>Enable revision-based versions, with minimal opportunity for error.</li>
<li>Limit custom code to absolute minimum. (Partially redundant with limiting dynamic metadata.)</li>
</ol>
<p>To operationalize (1), the following approaches should all install correctly:</p>
<ul>
<li><code>pip install .</code></li>
<li><code>python setup.py sdist &amp;&amp; pip install dist/*.tar.gz</code></li>
<li><code>python setup.py bdist_wheel &amp;&amp; pip install dist/*.whl</code></li>
<li><code>python setup.py install</code></li>
</ul>
<p>And development/editable mode should work:</p>
<ul>
<li><code>python setup.py develop</code></li>
<li><code>pip install -e .</code></li>
</ul>
<p>This also means that newer, better build systems that do not rely on setuptools are not really under consideration here.</p>
<p>To operationalize (3), all of the above should produce an install with the same version string, and setting the version should be done from a version control tag if possible. Assuming a <code>git</code> repository, the following should also work:</p>
<ul>
<li><code>git archive -o archive.tar.gz $TAG &amp;&amp; pip install archive.tar.gz</code></li>
</ul>
</section>
<section id="recommendations" class="level2">
<h2 class="anchored" data-anchor-id="recommendations">Recommendations</h2>
<p>I recommend on a <a href="https://setuptools.readthedocs.io/en/latest/">setuptools</a>-based approach, using <code>setup.cfg</code> to declare as much of the metadata as possible, along with an OPTIONAL <code>pyproject.toml</code> laid out in <a href="https://www.python.org/dev/peps/pep-0518/">PEP 518</a>. <a href="https://github.com/warner/python-versioneer">Versioneer</a> is used to handle versioning.</p>
<section id="pyproject.toml" class="level3">
<h3 class="anchored" data-anchor-id="pyproject.toml"><code>pyproject.toml</code></h3>
<p>A bare minimum <code>pyproject.toml</code> is as follows:</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode toml code-with-copy"><code class="sourceCode toml"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">[build-system]</span></span>
<span id="cb1-2"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">requires</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"setuptools &gt;= 30.3.0"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"wheel"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span></code></pre></div>
<p>Additional build dependencies such as <code>cython</code> and <code>numpy</code> might be put here.</p>
<p>I would relax this to a mere suggestion this year. While it’s mostly been fine, I have seen a case where <code>pip install -e --user .</code> fails, so it’s not as consequence-free as I thought.</p>
</section>
<section id="setup.cfg" class="level3">
<h3 class="anchored" data-anchor-id="setup.cfg"><code>setup.cfg</code></h3>
<p>As of setuptools 30.3.0, most packaging metadata can be set declaratively in <a href="https://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files"><code>setup.cfg</code></a>.</p>
<p>The following skeleton can be used as a model.</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode ini code-with-copy"><code class="sourceCode ini"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">[metadata]</span></span>
<span id="cb2-2"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">url </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> https://github.com/your/package</span></span>
<span id="cb2-3"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">author </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> You</span></span>
<span id="cb2-4"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">author_email </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> your@email.tld</span></span>
<span id="cb2-5"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">maintainer </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> You</span></span>
<span id="cb2-6"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">maintainer_email </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> your@email.tld</span></span>
<span id="cb2-7"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">description </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> A package</span></span>
<span id="cb2-8"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">long_description </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> file:README.rst</span></span>
<span id="cb2-9"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">long_description_content_type </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> text/x-rst; charset=UTF-8</span></span>
<span id="cb2-10"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">license </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> GPL</span></span>
<span id="cb2-11"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">classifiers </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span></span>
<span id="cb2-12"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">    Programming Language :: Python</span></span>
<span id="cb2-13"></span>
<span id="cb2-14"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">[options]</span></span>
<span id="cb2-15"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">python_requires </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> &gt;= 3.6</span></span>
<span id="cb2-16"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">install_requires </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span></span>
<span id="cb2-17"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">packages </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> find:</span></span>
<span id="cb2-18"></span>
<span id="cb2-19"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">[options.package_data]</span></span>
<span id="cb2-20"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">* </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span></span>
<span id="cb2-21"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">    data/*</span></span>
<span id="cb2-22"></span>
<span id="cb2-23"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">[options.extras_require]</span></span>
<span id="cb2-24"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">doc </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span></span>
<span id="cb2-25"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">    sphinx</span></span>
<span id="cb2-26"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">test </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span></span>
<span id="cb2-27"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">    pytest</span></span>
<span id="cb2-28"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">    coverage</span></span>
<span id="cb2-29"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">all </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span></span>
<span id="cb2-30"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">    %(doc)s</span></span>
<span id="cb2-31"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">    %(test)s</span></span></code></pre></div>
<p>I recommend <em>against</em> using the <code>include_package_data</code> option, which counterintuitively overrides the <code>package_data</code> options with the directives in <code>MANIFEST.in</code>.</p>
<p>I want to draw attention to the <code>python_requires</code> metadata which will prevent <code>pip</code> from attempting to install on incompatible systems. When you drop 3.5 - or any other versions - update the <code>python_requires</code> to avoid breaking downstream tools that still install on unsupported versions.</p>
<p>In addition to plain key-value pairs, there are some constrained options for common dynamic metadata. For example, <code>long_description = file:&lt;filename&gt;</code> allows you to place a long description in a separate file, to be included in your documentation. <code>packages = find:</code> replaces the <code>find_packages()</code> option often used in <code>setup.py</code>. Finally, interpolated strings are used in <code>extras_require</code> to provide a meta-extra like <code>all</code>.</p>
<p>I recommend not placing the version in <code>setup.cfg</code>.</p>
</section>
<section id="setup.py" class="level3">
<h3 class="anchored" data-anchor-id="setup.py"><code>setup.py</code></h3>
<p>The dynamic components of my package setup are as follows:</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#!/usr/bin/env python</span></span>
<span id="cb3-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> sys</span>
<span id="cb3-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> setuptools <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> setup</span>
<span id="cb3-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> versioneer</span>
<span id="cb3-5"></span>
<span id="cb3-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Give setuptools a hint to complain if it's too old a version</span></span>
<span id="cb3-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 30.3.0 allows us to put most metadata in setup.cfg</span></span>
<span id="cb3-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Should match pyproject.toml</span></span>
<span id="cb3-9">SETUP_REQUIRES <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'setuptools &gt;= 30.3.0'</span>]</span>
<span id="cb3-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># This enables setuptools to install wheel on-the-fly</span></span>
<span id="cb3-11">SETUP_REQUIRES <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'wheel'</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'bdist_wheel'</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">in</span> sys.argv <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">else</span> []</span>
<span id="cb3-12"></span>
<span id="cb3-13"><span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">__name__</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'__main__'</span>:</span>
<span id="cb3-14">    setup(name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'package'</span>,</span>
<span id="cb3-15">          version<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>versioneer.get_version(),</span>
<span id="cb3-16">          cmdclass<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>versioneer.get_cmdclass(),</span>
<span id="cb3-17">          setup_requires<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>SETUP_REQUIRES,</span>
<span id="cb3-18">          )</span></code></pre></div>
<p>I place the package name in <code>setup.py</code> mostly because, without this, GitHub will not recognize your package to place it in its dependency graphs.</p>
<p>By using <code>versioneer</code> in <code>setup.py</code> as opposed to adding <code>version = attr:package.__version__</code> to the <code>setup.cfg</code>, we avoid the issue of missing import-time dependencies. <code>versioneer.get_cmdclass()</code> tells <code>setuptools</code> how to encode the current version into various installation methods.</p>
<p>Finally, <code>setup_requires</code> is mostly here as a fall-back to let old versions of setuptools provide a user-readable explanation for failures.</p>
</section>
<section id="versioneer" class="level3">
<h3 class="anchored" data-anchor-id="versioneer">Versioneer</h3>
<p>Versioneer will set the version based on your git tag, and handle all of the install cases I described in desiderata.</p>
<p>This requires an additional section to your <code>setup.cfg</code>:</p>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode ini code-with-copy"><code class="sourceCode ini"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">[versioneer]</span></span>
<span id="cb4-2"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">VCS </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> git</span></span>
<span id="cb4-3"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">style </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> pep440</span></span>
<span id="cb4-4"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">versionfile_source </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> package/_version.py</span></span>
<span id="cb4-5"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">versionfile_build </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> package/_version.py</span></span>
<span id="cb4-6"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">tag_prefix </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span></span>
<span id="cb4-7"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">parentdir_prefix </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span></span></code></pre></div>
<p>It can then be installed from your repository root with:</p>
<pre class="shell"><code>pip install versioneer
versioneer install</code></pre>
<p>Once done, it places a copy of itself in your repository root, so other users do <em>not</em> need to install it for it to be used correctly.</p>
<p>N.B. Versioneer does not work out of the box with git archives for non-tag releases. If you need any archived revision, this will not be sufficient. I don’t know of a general solution to that problem at this point, as <code>git archive</code> substitution is quite limited.</p>
</section>
<section id="package-data-manifest.in" class="level3">
<h3 class="anchored" data-anchor-id="package-data-manifest.in">Package Data / <code>MANIFEST.in</code></h3>
<p>This was probably the most confusing and thing to nail down, so I want to lay it out clearly.</p>
<p>The <code>package_data</code> metadata determines what data files inside your package directory will follow your Python files into their install location. Which is the same as saying these files will be packaged in a wheel, as that is (more-or-less) unzipped into your <code>site-packages/</code> directory.</p>
<p><code>MANIFEST.in</code> determines what data files are included in your sdist <em>on top of</em> what is included in your <code>package_data</code>. Use it to include anything outside your package directory that you want included in source. Note that there are some <a href="https://docs.python.org/3/distutils/sourcedist.html#specifying-the-files-to-distribute">defaults</a> that you don’t need to specify.</p>
<p><strong>DO NOT</strong> use <code>include_package_data = True</code>. That will change the rules of how this all works to something even less intuitive.</p>
</section>
</section>
<section id="dependencies" class="level2">
<h2 class="anchored" data-anchor-id="dependencies">Dependencies</h2>
<p>The minimum <code>setuptools</code> version needed for <code>setup.cfg</code> to work is <a href="https://setuptools.readthedocs.io/en/latest/history.html#v30-3-0">30.3.0</a>, although more fields have been defined since then, and the minimum <code>pip</code> needed for PEP 518 compatibility is <a href="https://pip.pypa.io/en/stable/news/#id166">10.0.0</a>.</p>
</section>
<section id="notes-on-new-build-systems-and-legacy-operating-systems" class="level2">
<h2 class="anchored" data-anchor-id="notes-on-new-build-systems-and-legacy-operating-systems">Notes on new build systems and legacy operating systems</h2>
<p>As noted above, setuptools was the only system under serious consideration, simply because it has long been the standard to run <code>python setup.py</code>. Until pip 10+ is universal, alternative build systems will create headaches that I don’t want to deal with.</p>
<p>CentOS 7, for instance still packages pip 7.1 and setuptools 0.9.8, which means the above will not work out of the box (though this may be changing… I’m having a hard time reading pkgs.org). However, sticking with setuptools and <code>setup_requires</code> ensures that a user will at least be told to upgrade setuptools.</p>
</section>
<section id="references" class="level2">
<h2 class="anchored" data-anchor-id="references">References</h2>
<section id="peps" class="level3">
<h3 class="anchored" data-anchor-id="peps">PEPs</h3>
<ul>
<li><a href="https://www.python.org/dev/peps/pep-0440/">440</a></li>
<li><a href="https://www.python.org/dev/peps/pep-0508/">508</a></li>
<li><a href="https://www.python.org/dev/peps/pep-0517/">517</a></li>
<li><a href="https://www.python.org/dev/peps/pep-0518/">518</a></li>
</ul>
</section>
<section id="other-links" class="level3">
<h3 class="anchored" data-anchor-id="other-links">Other links</h3>
<ul>
<li><a href="https://setuptools.readthedocs.io/en/latest/setuptools.html">Building and Distributing Packages with Setuptools</a></li>
</ul>
</section>
</section>


<div id="quarto-appendix" class="default"><section id="license" class="level2 appendix"><h2 class="anchored quarto-appendix-heading">License</h2><div class="quarto-appendix-contents">

<p>To the extent copyright can be claimed, I disclaim it under <a href="https://creativecommons.org/publicdomain/zero/1.0/">CC0</a>.</p>


</div></section></div> ]]></description>
  <category>python</category>
  <category>code</category>
  <category>packaging</category>
  <guid>https://effigies.gitlab.io/posts/python-packaging-2020/</guid>
  <pubDate>Mon, 13 Jul 2020 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Contemporary Python Packaging (2019)</title>
  <dc:creator>Chris Markiewicz</dc:creator>
  <link>https://effigies.gitlab.io/posts/python-packaging-2019/</link>
  <description><![CDATA[ 




<p>This document lays out a set of Python packaging practices. I don’t claim they are <em>best</em> practices, but they fit my needs, and might fit yours.</p>
<p>This was previously published as a GitHub <a href="https://gist.github.com/effigies/c9f4194034ee218bab1668bfd7851cfc">gist</a>.</p>
<section id="validity" class="level2">
<h2 class="anchored" data-anchor-id="validity">Validity</h2>
<p>This document has been <a href="../python-packaging-2020/">superseded as of July 2020</a>.</p>
<p>This was written in July 2019. As of this writing Python 2.7 and Python 3.5 still have not reached end-of-life. This document should be superseded or disregarded no later than the Python 3.6 end-of-life. If you cite this as a justification for your behavior, please stop doing so at that time.</p>
</section>
<section id="summary" class="level2">
<h2 class="anchored" data-anchor-id="summary">Summary</h2>
<ul>
<li>For versioning, use <a href="https://github.com/warner/python-versioneer">versioneer</a></li>
<li>For describing your build requirements, use <a href="https://www.python.org/dev/peps/pep-0518/#build-system-table"><code>pyproject.toml</code></a></li>
<li>For all static (and some dynamic-ish) metadata, use <a href="https://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files"><code>setup.cfg</code></a></li>
<li>A very small <code>setup.py</code> for dynamic metadata and to tie everything together</li>
<li>Go ahead and keep <code>MANIFEST.in</code> for now</li>
</ul>
</section>
<section id="perspective" class="level2">
<h2 class="anchored" data-anchor-id="perspective">Perspective</h2>
<p>My position in the Python ecosystem will color my perspective and approach to packaging, and, presumably, how much weight you give what I think.</p>
<p>I am most active with the <a href="https://nipy.org">NIPY</a> collection of projects, generally related to neuroimaging and neuroscience. I am currently the lead maintainer of <a href="https://github.com/nipy/nibabel">NiBabel</a> and do a reasonable amount of maintenance work for <a href="https://github.com/nipy/nipype">Nipype</a>, <a href="https://github.com/bids-standard/pybids">PyBIDS</a>, <a href="https://github.com/poldracklab/fmriprep">fMRIPrep</a> and a few other packages closely related to the aforementioned.</p>
<p>I am not an active developer in <a href="https://github.com/python/cpython">CPython</a>, <a href="https://github.com/pypa/">PyPA</a> or any packaging-related tools. I have not followed deep arguments, but have relied mostly on PEPs, documentation and sporadic searches to identify the current state of the art.</p>
<p>So my perspective is less concerned with what packaging should become, and more with what works today and where things appear to be heading, as I look to prepare or update packaging infrastructure for several tools. Infrastructure I hope to stop thinking about so much.</p>
</section>
<section id="desiderata" class="level2">
<h2 class="anchored" data-anchor-id="desiderata">Desiderata</h2>
<p>Motivating my recommendations are a few desiderata, in rough order of importance:</p>
<ol type="1">
<li>Installation should work, from source, on fairly old systems. Debian Stable (9) is my touchstone here.</li>
<li>Prefer declarative syntax, and limit dynamic metadata, as much as possible.</li>
<li>Enable revision-based versions, with minimal opportunity for error.</li>
<li>Limit custom code to absolute minimum. (Partially redundant with limiting dynamic metadata.)</li>
</ol>
<p>To operationalize (1), the following approaches should all install correctly:</p>
<ul>
<li><code>python setup.py install</code></li>
<li><code>pip install .</code></li>
<li><code>python setup.py sdist &amp;&amp; pip install dist/*.tar.gz</code></li>
<li><code>python setup.py bdist_wheel &amp;&amp; pip install dist/*.whl</code></li>
</ul>
<p>And development/editable mode should work:</p>
<ul>
<li><code>python setup.py develop</code></li>
<li><code>pip install -e .</code></li>
</ul>
<p>This also means that newer, better build systems that do not rely on setuptools are not really under consideration here.</p>
<p>To operationalize (3), all of the above should produce an install with the same version string, and setting the version should be done from a version control tag if possible. Assuming a <code>git</code> repository, the following should also work:</p>
<ul>
<li><code>git archive -o archive.tar.gz $TAG &amp;&amp; pip install archive.tar.gz</code></li>
</ul>
</section>
<section id="recommendations" class="level2">
<h2 class="anchored" data-anchor-id="recommendations">Recommendations</h2>
<p>I have settled on a <a href="https://setuptools.readthedocs.io/en/latest/">setuptools</a>-based approach, using <code>setup.cfg</code> to declare as much of the metadata as possible, along with <code>pyproject.toml</code> laid out in <a href="https://www.python.org/dev/peps/pep-0518/">PEP 518</a>. <a href="https://github.com/warner/python-versioneer">Versioneer</a> is used to handle versioning.</p>
<section id="pyproject.toml" class="level3">
<h3 class="anchored" data-anchor-id="pyproject.toml"><code>pyproject.toml</code></h3>
<p>A bare minimum <code>pyproject.toml</code> is as follows:</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode toml code-with-copy"><code class="sourceCode toml"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">[build-system]</span></span>
<span id="cb1-2"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">requires</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"setuptools &gt;= 30.3.0"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"wheel"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span></code></pre></div>
<p>Additional build dependencies such as <code>cython</code> and <code>numpy</code> might be put here.</p>
</section>
<section id="setup.cfg" class="level3">
<h3 class="anchored" data-anchor-id="setup.cfg"><code>setup.cfg</code></h3>
<p>As of setuptools 30.3.0, most packaging metadata can be set declaratively in <a href="https://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files"><code>setup.cfg</code></a>.</p>
<p>The following skeleton can be used as a model.</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode ini code-with-copy"><code class="sourceCode ini"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">[metadata]</span></span>
<span id="cb2-2"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">url </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> https://github.com/your/package</span></span>
<span id="cb2-3"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">author </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> You</span></span>
<span id="cb2-4"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">author_email </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> your@email.tld</span></span>
<span id="cb2-5"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">maintainer </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> You</span></span>
<span id="cb2-6"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">maintainer_email </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> your@email.tld</span></span>
<span id="cb2-7"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">description </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> A package</span></span>
<span id="cb2-8"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">long_description </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> file:README.rst</span></span>
<span id="cb2-9"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">long_description_content_type </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> text/x-rst; charset=UTF-8</span></span>
<span id="cb2-10"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">license </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> GPL</span></span>
<span id="cb2-11"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">classifiers </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span></span>
<span id="cb2-12"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">    Programming Language :: Python</span></span>
<span id="cb2-13"></span>
<span id="cb2-14"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">[options]</span></span>
<span id="cb2-15"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">python_requires </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> &gt;= 3.5</span></span>
<span id="cb2-16"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">install_requires </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span></span>
<span id="cb2-17"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">test_requires </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span></span>
<span id="cb2-18"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">    pytest</span></span>
<span id="cb2-19"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">    coverage</span></span>
<span id="cb2-20"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">packages </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> find:</span></span>
<span id="cb2-21"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">include_package_data </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb2-22"></span>
<span id="cb2-23"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">[options.package_data]</span></span>
<span id="cb2-24"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">* </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span></span>
<span id="cb2-25"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">    data/*</span></span>
<span id="cb2-26"></span>
<span id="cb2-27"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">[options.extras_require]</span></span>
<span id="cb2-28"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">doc </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span></span>
<span id="cb2-29"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">    sphinx</span></span>
<span id="cb2-30"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">test </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span></span>
<span id="cb2-31"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">    pytest</span></span>
<span id="cb2-32"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">    coverage</span></span>
<span id="cb2-33"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">all </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span></span>
<span id="cb2-34"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">    %(doc)s</span></span>
<span id="cb2-35"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">    %(test)s</span></span></code></pre></div>
<p>I want to draw attention to the <code>python_requires</code> metadata which will prevent <code>pip</code> from attempting to install on incompatible systems. When you drop 2.7 and 3.4 - or any other versions - update the <code>python_requires</code> to avoid breaking downstream tools that still support those versions.</p>
<p>In addition to plain key-value pairs, there are some constrained options for common dynamic metadata. For example, <code>long_description = file:&lt;filename&gt;</code> allows you to place a long description in a separate file, to be included in your documentation. <code>packages = find:</code> replaces the <code>find_packages()</code> option often used in <code>setup.py</code>. Finally, interpolated strings are used in <code>extras_require</code> to provide a meta-extra like <code>all</code>.</p>
<p>I recommend not placing the version in <code>setup.cfg</code>.</p>
</section>
<section id="setup.py" class="level3">
<h3 class="anchored" data-anchor-id="setup.py"><code>setup.py</code></h3>
<p>The dynamic components of my package setup are as follows:</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#!/usr/bin/env python</span></span>
<span id="cb3-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> sys</span>
<span id="cb3-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> setuptools <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> setup</span>
<span id="cb3-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> versioneer</span>
<span id="cb3-5"></span>
<span id="cb3-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Give setuptools a hint to complain if it's too old a version</span></span>
<span id="cb3-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 30.3.0 allows us to put most metadata in setup.cfg</span></span>
<span id="cb3-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Should match pyproject.toml</span></span>
<span id="cb3-9">SETUP_REQUIRES <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'setuptools &gt;= 30.3.0'</span>]</span>
<span id="cb3-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># This enables setuptools to install wheel on-the-fly</span></span>
<span id="cb3-11">SETUP_REQUIRES <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'wheel'</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'bdist_wheel'</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">in</span> sys.argv <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">else</span> []</span>
<span id="cb3-12"></span>
<span id="cb3-13"><span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">__name__</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'__main__'</span>:</span>
<span id="cb3-14">    setup(name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'package'</span>,</span>
<span id="cb3-15">          version<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>versioneer.get_version(),</span>
<span id="cb3-16">          cmdclass<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>versioneer.get_cmdclass(),</span>
<span id="cb3-17">          setup_requires<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>SETUP_REQUIRES,</span>
<span id="cb3-18">          )</span></code></pre></div>
<p>I place the package name in <code>setup.py</code> mostly because, without this, GitHub will not recognize your package to place it in its dependency graphs.</p>
<p>By using <code>versioneer</code> in <code>setup.py</code> as opposed to adding <code>version = attr:package.__version__</code> to the <code>setup.cfg</code>, we avoid the issue of missing import-time dependencies. <code>versioneer.get_cmdclass()</code> tells <code>setuptools</code> how to encode the current version into various installation methods.</p>
<p>Finally, <code>setup_requires</code> is mostly here as a fall-back to let old versions of setuptools provide a user-readable explanation for failures.</p>
</section>
<section id="versioneer" class="level3">
<h3 class="anchored" data-anchor-id="versioneer">Versioneer</h3>
<p>Versioneer will set the version based on your git tag, and handle all of the install cases I described in desiderata.</p>
<p>This requires an additional section to your <code>setup.cfg</code>:</p>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode ini code-with-copy"><code class="sourceCode ini"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">[versioneer]</span></span>
<span id="cb4-2"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">VCS </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> git</span></span>
<span id="cb4-3"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">style </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> pep440</span></span>
<span id="cb4-4"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">versionfile_source </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> package/_version.py</span></span>
<span id="cb4-5"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">versionfile_build </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> package/_version.py</span></span>
<span id="cb4-6"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">tag_prefix </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span></span>
<span id="cb4-7"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">parentdir_prefix </span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span></span></code></pre></div>
<p>It can then be installed from your repository root with:</p>
<pre class="shell"><code>pip install versioneer
versioneer install</code></pre>
<p>Once done, it places a copy of itself in your repository root, so other users do <em>not</em> need to install it for it to be used correctly.</p>
<p>N.B. Versioneer does not work out of the box with git archives for non-tag releases. If you need any archived revision, this will not be sufficient. I don’t know of a general solution to that problem at this point, as <code>git archive</code> substitution is quite limited.</p>
</section>
<section id="manifest.in" class="level3">
<h3 class="anchored" data-anchor-id="manifest.in"><code>MANIFEST.in</code></h3>
<p>I had hoped to be able to eliminate <a href="https://docs.python.org/3/distutils/sourcedist.html#specifying-the-files-to-distribute"><code>MANIFEST.in</code></a>, but source distributions (<code>sdist</code>s) do not seem able to package correctly without it. It is almost certainly possible to write a helper function in <code>setup.py</code> that will populate it from the <code>setup.cfg</code> metadata, but a new batch of custom code feels somewhat counter to the spirit of this endeavor. I’ve elected to simply wait for a couple more years and re-assess.</p>
</section>
</section>
<section id="dependencies" class="level2">
<h2 class="anchored" data-anchor-id="dependencies">Dependencies</h2>
<p>The minimum <code>setuptools</code> version needed for <code>setup.cfg</code> to work is <a href="https://setuptools.readthedocs.io/en/latest/history.html#v30-3-0">30.3.0</a>, although more fields have been defined since then, and the minimum <code>pip</code> needed for PEP 518 compatibility is <a href="https://pip.pypa.io/en/stable/news/#id166">10.0.0</a>.</p>
</section>
<section id="notes-on-new-build-systems-and-legacy-operating-systems" class="level2">
<h2 class="anchored" data-anchor-id="notes-on-new-build-systems-and-legacy-operating-systems">Notes on new build systems and legacy operating systems</h2>
<p>As noted above, setuptools was the only system under serious consideration, simply because it has long been the standard to run <code>python setup.py</code>. Until pip 10+ is universal, alternative build systems will create headaches that I don’t want to deal with.</p>
<p>CentOS 7, for instance still packages pip 7.1 and setuptools 0.9.8, which means the above will not work out of the box. However, sticking with setuptools and <code>setup_requires</code> ensures that a user will at least be told to upgrade setuptools.</p>
</section>
<section id="references" class="level2">
<h2 class="anchored" data-anchor-id="references">References</h2>
<section id="peps" class="level3">
<h3 class="anchored" data-anchor-id="peps">PEPs</h3>
<ul>
<li><a href="https://www.python.org/dev/peps/pep-0440/">440</a></li>
<li><a href="https://www.python.org/dev/peps/pep-0508/">508</a></li>
<li><a href="https://www.python.org/dev/peps/pep-0517/">517</a></li>
<li><a href="https://www.python.org/dev/peps/pep-0518/">518</a></li>
</ul>
</section>
<section id="other-links" class="level3">
<h3 class="anchored" data-anchor-id="other-links">Other links</h3>
<ul>
<li><a href="https://setuptools.readthedocs.io/en/latest/setuptools.html">Building and Distributing Packages with Setuptools</a></li>
</ul>
</section>
</section>


<div id="quarto-appendix" class="default"><section id="license" class="level2 appendix"><h2 class="anchored quarto-appendix-heading">License</h2><div class="quarto-appendix-contents">

<p>To the extent copyright can be claimed, I disclaim it under <a href="https://creativecommons.org/publicdomain/zero/1.0/">CC0</a>.</p>


</div></section></div> ]]></description>
  <category>python</category>
  <category>code</category>
  <category>packaging</category>
  <guid>https://effigies.gitlab.io/posts/python-packaging-2019/</guid>
  <pubDate>Wed, 31 Jul 2019 00:00:00 GMT</pubDate>
</item>
</channel>
</rss>
