<?xml version="1.0" encoding="utf-8" ?>
<feed xmlns="http://www.w3.org/2005/Atom"
      xmlns:dc="http://purl.org/dc/elements/1.1/"
      xml:base="https://reinout.vanrees.org/" xml:lang="en">
  <link rel="self"
        href="https://reinout.vanrees.org/weblog/plonefeed.xml" />
  <link href="https://reinout.vanrees.org/weblog/"
        rel="alternate" type="text/html" />

  <title type="html">Reinout van Rees' weblog</title>
  <subtitle>Python, grok, books, history, faith, etc.</subtitle>
  <updated>2026-04-02T20:05:00+01:00</updated>
  <id>urn:syndication:a55644db8591c020bd38852775819a9a</id>

  
    <entry>
      <title>Python Leiden (NL) meetup: building apps with streamlit - Daniël Kentrop</title>
      <link rel="alternate" type="text/html"
            href="https://reinout.vanrees.org/weblog/2026/04/02/3-streamlit.html" />
      <id>http://reinout.vanrees.org/weblog/2026/04/02/3-streamlit.html</id>
      <!-- id is not https: prevents old entries from showing up again -->
      <author>
        <name>Reinout van Rees</name>
      </author>
      <published>2026-04-02T00:00:00+01:00</published>
      <updated>2026-04-02T20:05:00+01:00</updated>

      
        <category term="python" />
      
        <category term="pun" />
      

      <content type="html"><![CDATA[
      <div class="document">
<p>(One of <a class="reference external" href="https://reinout.vanrees.org/weblog/2026/04/02/index.html">my summaries</a> of
the <a class="reference external" href="https://www.meetup.com/python-leiden-user-group/events/313010112/">Leiden (NL) Python meetup</a>).</p>
<p>Daniël is a civil engineer turned software developer. He works for a company involved in
improving the 17000 km of dikes in the Netherlands. He liked programming, but
interfacing with his colleagues was a bit problematic. Jupyter notebooks without a venv?
Interfaces with QT (which explode in size)? PyInstaller? In the end, he often opted for
data in an excel sheet and then running a python script...</p>
<p>He now likes to use <a class="reference external" href="https://streamlit.io/">streamlit</a>, a Python library for creating
simple web apps, prototyping, visualisation. It has lots of build-in elements and
widgets. Data entry, all sorts of (plotly) charts, sliders, selectboxes, pop-ups,
basically everything you need.</p>
<p>You can add custom components with html/css/js.</p>
<p>How does it work? It is basically a script. The whole page is loaded every time and
re-run. A widget interaction means a re-run. Pressing a button means a re-run. There
<em>is</em> state you can store on the server per session. He showed a demo demonstrating the
problems caused by the constant re-running (and losing of state) and how to solve it
with the session state.</p>
<p>He then showed a bigger streamlit demo. On a map, you could draw an area and select
water level measurement stations and then show water levels of the last month. Nice.</p>
<p>An upcoming change to streamlit: they're going to move from the Tornado web runner to
Starlette, which also means ASGI support.</p>
</div>

      ]]>
      </content>

    </entry>
  
    <entry>
      <title>Python Leiden (NL) meetup: newwave, python package setup and cpython contribution - Michiel Beijen</title>
      <link rel="alternate" type="text/html"
            href="https://reinout.vanrees.org/weblog/2026/04/02/2-newwave.html" />
      <id>http://reinout.vanrees.org/weblog/2026/04/02/2-newwave.html</id>
      <!-- id is not https: prevents old entries from showing up again -->
      <author>
        <name>Reinout van Rees</name>
      </author>
      <published>2026-04-02T00:00:00+01:00</published>
      <updated>2026-04-02T19:16:00+01:00</updated>

      
        <category term="python" />
      
        <category term="pun" />
      

      <content type="html"><![CDATA[
      <div class="document">
<p>(One of <a class="reference external" href="https://reinout.vanrees.org/weblog/2026/04/02/index.html">my summaries</a> of
the <a class="reference external" href="https://www.meetup.com/python-leiden-user-group/events/313010112/">Leiden (NL) Python meetup</a>).</p>
<p>Wave audio is more or less the original representation of digital audio. <tt class="docutils literal">.wav</tt> format
(there are competing formats like <tt class="docutils literal">.au</tt> or <tt class="docutils literal">.aiff</tt>). Typically it is &quot;PCM-encoded&quot;,
just as what comes out of the CD.</p>
<p>The .wav format was introduced by microsoft in windows 3.1 in 1992. But... it is still
relevant for audio production or podcasts. And: for lab equipment. The company Michiel
works for (<a class="reference external" href="https://samotics.com/">https://samotics.com/</a>, sponsor of the meetup's pizza, thanks!) records the
sound of big motors and other big equipment for analysis purposes. They use .wav for
this.</p>
<p>Around 1992, Python also started to exist. Version 1.0 (1994) included the &quot;wave&quot; module
in its standard library. But: it is only intended for regular .wav usage, so two stereo
channels and max 44.1 kHz frequency. He needed three channels (for sound recordings for
the three phases of the electrical motor) and he needed higher resolution.</p>
<p>He showed that you could actually put three channels into a .wav with Python. Audacity
loaded it just fine, but the &quot;flac&quot; encoder refused it, with an error about a missing
<tt class="docutils literal">WAVE_FORMAT_EXTENSIBLE</tt> setting. He started investigating the python bugtracker and
discovered someone had already provided a fix for <em>reading</em> wav files in that extended
format.</p>
<p>So he dug further. And discovered some bugs himself and reported them. And found
undocumented methods and reported them. So after reports, you should try fixing them
with a pull request. He discovered a half-finished PR and worked on basis of that. Lots
of discussion in the ticket, but in the end it got merged. Another bugfix also got
merged. Hurray!</p>
<p>But... those fixes will end up in Python 3.15, October 2026. And his company is just
moving from 3.10 to 3.11... So he made a library out of it at
<a class="reference external" href="https://codeberg.org/michielb/newwave">https://codeberg.org/michielb/newwave</a> . (And he put in some good words for
<a class="reference external" href="https://codeberg.org">https://codeberg.org</a> , as that's a nice github alternative: operated by volunteers under
a German Foundation instead of we-have-put-all-our-open-source-eggs-in-one-basket's
Github, being owned by Microsoft/USA).</p>
</div>

      ]]>
      </content>

    </entry>
  
    <entry>
      <title>Python Leiden (NL) meetup: creating QR codes with python - Rob Zwartenkot</title>
      <link rel="alternate" type="text/html"
            href="https://reinout.vanrees.org/weblog/2026/04/02/1-qr.html" />
      <id>http://reinout.vanrees.org/weblog/2026/04/02/1-qr.html</id>
      <!-- id is not https: prevents old entries from showing up again -->
      <author>
        <name>Reinout van Rees</name>
      </author>
      <published>2026-04-02T00:00:00+01:00</published>
      <updated>2026-04-02T18:36:00+01:00</updated>

      
        <category term="python" />
      
        <category term="pun" />
      

      <content type="html"><![CDATA[
      <div class="document">
<p>(One of <a class="reference external" href="https://reinout.vanrees.org/weblog/2026/04/02/index.html">my summaries</a> of
the <a class="reference external" href="https://www.meetup.com/python-leiden-user-group/events/313010112/">Leiden (NL) Python meetup</a>).</p>
<p>Qr codes are <em>everywhere</em>. They're used to transport data, the best example is a link to
a website, but you can use them for a lot of things. A nice different usage is a WIFI
connection string, something like <tt class="docutils literal">WIFI:T:WPA;S:NetworkName;p:password;;</tt> . Rob
focuses on the url kind.</p>
<p>There's a standard, ISO/IEC 18004. QR codes need to be square. With a bit of a margin
around it. The cells should also be square. You sometimes see somewhat rounded cells,
but that's not according to the standard. You sometimes see a logo in the middle, but
that actually destroys data! Luckily there's error correction in the standard, that's
the only reason why it works. There's more to QR codes than you think!</p>
<p>He uses the <a class="reference external" href="https://pypi.org/project/segno/">segno</a> as qr code library (instead of
&quot;qrcode&quot;). It is more complete, allows multiple output formats, it can control error
correction:</p>
<pre class="literal-block">
import segno
qr = segno.make(&quot;https://pythonleiden.nl/&quot;)
qr.save(&quot;leiden.png&quot;)
</pre>
<p>Such an image is very small. If you scale it, it gets blurry. And there's no border and
no error correction. We can do better:</p>
<pre class="literal-block">
import segno
qr = segno.make(&quot;https://pythonleiden.nl/&quot;, error=&quot;h&quot;)
# &quot;h&quot; is the &quot;high&quot; level of error correction, it allows
# for up to 30% corruption.
qr.save(
    &quot;leiden.png&quot;,
    scale=10,
    border=4,
)
</pre>
<p>Segno can also give you the raw matix of cells. That way you can do some further
processing on it. For instance with PIL (the Python Imaging Library). As an example, he
placed a logo in the middle of the QR code.</p>
<p>How you can work with the matrix:</p>
<pre class="literal-block">
... same as before ...
for line in qr.matrix:
    for cell in line:
        ....
</pre>
<p>He went totally overboard with round dots and colors and a logo in the middle. At least
on my phone, it still worked! Funny.</p>
<img alt="https://reinout.vanrees.org/images/2026/qr-example.png" src="https://reinout.vanrees.org/images/2026/qr-example.png" />
</div>

      ]]>
      </content>

    </entry>
  
    <entry>
      <title>Tombi, pre-commit, prek and uv.lock</title>
      <link rel="alternate" type="text/html"
            href="https://reinout.vanrees.org/weblog/2026/03/18/tombi-precommit.html" />
      <id>http://reinout.vanrees.org/weblog/2026/03/18/tombi-precommit.html</id>
      <!-- id is not https: prevents old entries from showing up again -->
      <author>
        <name>Reinout van Rees</name>
      </author>
      <published>2026-03-18T00:00:00+01:00</published>
      <updated>2026-03-18T15:54:00+01:00</updated>

      
        <category term="python" />
      
        <category term="django" />
      

      <content type="html"><![CDATA[
      <div class="document">
<p>In almost all my Python projects, I'm using <strong>pre-commit</strong> to handle/check formatting and
linting. The advantage: pre-commit is the only tool you need to install. Pre-commit
itself reads its config file and installs the formatters and linters you defined in
there.</p>
<p>Here's a typical <tt class="docutils literal"><span class="pre">.pre-commit-config.yaml</span></tt>:</p>
<pre class="code yaml literal-block">
<span class="name tag">default_language_version</span><span class="punctuation">:</span><span class="whitespace">
  </span><span class="name tag">python</span><span class="punctuation">:</span><span class="whitespace"> </span><span class="literal scalar plain">python3</span><span class="whitespace">

</span><span class="name tag">repos</span><span class="punctuation">:</span><span class="whitespace">
  </span><span class="punctuation indicator">-</span><span class="whitespace"> </span><span class="name tag">repo</span><span class="punctuation">:</span><span class="whitespace"> </span><span class="literal scalar plain">https://github.com/pre-commit/pre-commit-hooks</span><span class="whitespace">
    </span><span class="name tag">rev</span><span class="punctuation">:</span><span class="whitespace"> </span><span class="literal scalar plain">v6.0.0</span><span class="whitespace">
    </span><span class="name tag">hooks</span><span class="punctuation">:</span><span class="whitespace">
      </span><span class="punctuation indicator">-</span><span class="whitespace"> </span><span class="name tag">id</span><span class="punctuation">:</span><span class="whitespace"> </span><span class="literal scalar plain">trailing-whitespace</span><span class="whitespace">
      </span><span class="punctuation indicator">-</span><span class="whitespace"> </span><span class="name tag">id</span><span class="punctuation">:</span><span class="whitespace"> </span><span class="literal scalar plain">end-of-file-fixer</span><span class="whitespace">
      </span><span class="punctuation indicator">-</span><span class="whitespace"> </span><span class="name tag">id</span><span class="punctuation">:</span><span class="whitespace"> </span><span class="literal scalar plain">check-yaml</span><span class="whitespace">
        </span><span class="name tag">args</span><span class="punctuation">:</span><span class="whitespace"> </span><span class="punctuation indicator">[</span><span class="name variable">--allow-multiple-documents</span><span class="punctuation indicator">]</span><span class="whitespace">
      </span><span class="punctuation indicator">-</span><span class="whitespace"> </span><span class="name tag">id</span><span class="punctuation">:</span><span class="whitespace"> </span><span class="literal scalar plain">check-toml</span><span class="whitespace">
      </span><span class="punctuation indicator">-</span><span class="whitespace"> </span><span class="name tag">id</span><span class="punctuation">:</span><span class="whitespace"> </span><span class="literal scalar plain">check-added-large-files</span><span class="whitespace">
  </span><span class="punctuation indicator">-</span><span class="whitespace"> </span><span class="name tag">repo</span><span class="punctuation">:</span><span class="whitespace"> </span><span class="literal scalar plain">https://github.com/astral-sh/ruff-pre-commit</span><span class="whitespace">
    </span><span class="comment single"># Ruff version.</span><span class="whitespace">
    </span><span class="name tag">rev</span><span class="punctuation">:</span><span class="whitespace"> </span><span class="literal scalar plain">v0.15.6</span><span class="whitespace">
    </span><span class="name tag">hooks</span><span class="punctuation">:</span><span class="whitespace">
      </span><span class="comment single"># Run the linter.</span><span class="whitespace">
      </span><span class="punctuation indicator">-</span><span class="whitespace"> </span><span class="name tag">id</span><span class="punctuation">:</span><span class="whitespace"> </span><span class="literal scalar plain">ruff</span><span class="whitespace">
        </span><span class="name tag">args</span><span class="punctuation">:</span><span class="whitespace"> </span><span class="punctuation indicator">[</span><span class="literal string">&quot;--fix&quot;</span><span class="punctuation indicator">]</span><span class="whitespace">
      </span><span class="comment single"># Run the formatter.</span><span class="whitespace">
      </span><span class="punctuation indicator">-</span><span class="whitespace"> </span><span class="name tag">id</span><span class="punctuation">:</span><span class="whitespace"> </span><span class="literal scalar plain">ruff-format</span><span class="whitespace">
  </span><span class="punctuation indicator">-</span><span class="whitespace"> </span><span class="name tag">repo</span><span class="punctuation">:</span><span class="whitespace"> </span><span class="literal scalar plain">https://github.com/tombi-toml/tombi-pre-commit</span><span class="whitespace">
    </span><span class="name tag">rev</span><span class="punctuation">:</span><span class="whitespace"> </span><span class="literal scalar plain">v0.9.6</span><span class="whitespace">
    </span><span class="name tag">hooks</span><span class="punctuation">:</span><span class="whitespace">
      </span><span class="punctuation indicator">-</span><span class="whitespace"> </span><span class="name tag">id</span><span class="punctuation">:</span><span class="whitespace"> </span><span class="literal scalar plain">tombi-format</span><span class="whitespace">
        </span><span class="name tag">args</span><span class="punctuation">:</span><span class="whitespace"> </span><span class="punctuation indicator">[</span><span class="literal string">&quot;--offline&quot;</span><span class="punctuation indicator">]</span><span class="whitespace">
      </span><span class="punctuation indicator">-</span><span class="whitespace"> </span><span class="name tag">id</span><span class="punctuation">:</span><span class="whitespace"> </span><span class="literal scalar plain">tombi-lint</span><span class="whitespace">
        </span><span class="name tag">args</span><span class="punctuation">:</span><span class="whitespace"> </span><span class="punctuation indicator">[</span><span class="literal string">&quot;--offline&quot;</span><span class="punctuation indicator">]</span>
</pre>
<p>The <strong>&quot;tombi&quot;</strong> at the end might be a bit curious. There's already the build-in
&quot;check-toml&quot; toml syntax checker, right? Well, <a class="reference external" href="https://tombi-toml.github.io/tombi">tombi</a> also does formatting and schema validation. And
in a recent project, I handled configuration through toml files.</p>
<p>It was for a Django website where several geographical maps were shown, each with its
own title, description, legend yes/no, etcetera. I made up a .toml configuration format
so that a colleague could configure all those maps without needing to deal with the
python code. I created a json schema as format specification (yes, json is funnily used
for that purpose). With tombi, I could make sure the config files were valid.</p>
<p>Oh, and tombi has an LSP plugin, so my colleague got autocomplete and syntax help out
of the box. nice.</p>
<p>I'm also using <a class="reference external" href="https://docs.astral.sh/uv/">uv</a> a lot. That generates an <tt class="docutils literal">uv.lock</tt>
file, in .toml format, with all the version pins. It is a toml file, but without the
<tt class="docutils literal">.toml</tt> extension. So pre-commit ignored it. <em>Until suddenly it started complaining
about the indentation</em>. But only in a github action, not locally.</p>
<p>Note: the complaint about the indentation is probably correct, as there's an issue <a class="reference external" href="https://github.com/astral-sh/uv/issues/5852">in
the uv bugtracker</a> about changing the
indentation from 4 to 2 in the lockfile.</p>
<p>The weird thing for me was that I pin the the versions of the plugins. So the behaviour
locally and on github should be the same. Some observations:</p>
<ul class="simple">
<li>Running <tt class="docutils literal">tombi</tt> from the commandline on <tt class="docutils literal">uv.lock</tt> resulted in re-formatting to two
spaces, whatever the tombi version.</li>
<li>Pre-commit locally did not re-format the file, but pre-commit on the server did.</li>
<li>I tried it with the new rust-based alternative for pre-commit, <strong>prek</strong> (see
<a class="reference external" href="https://github.com/j178/prek">https://github.com/j178/prek</a>), which <em>did</em> re-format uv.lock.</li>
</ul>
<p>Some further debugging showed that pre-commit was actually skipping the <tt class="docutils literal">uv.lock</tt>
file. But apparently not on github. I did some searching in pre-commit's source code and
tombi's <a class="reference external" href="https://github.com/tombi-toml/tombi-pre-commit/blob/main/.pre-commit-hooks.yaml">pre-commit hook definition</a>. The
only relevant part there was <tt class="docutils literal">types: [toml]</tt>. So somehow pre-commit has a definition
of what a toml file is. But I couldn't find anything.</p>
<p>Until I spotted that pre-commit uses <a class="reference external" href="https://pypi.org/project/identify/">identify</a> as
the means to detect file types. (Looks like a handy library, btw!). And that project had
<a class="reference external" href="https://github.com/pre-commit/identify/commit/93f1aa65b42af000df874bec3a01c645d5782c50">a change a couple of weeks ago</a>
that identifies <tt class="docutils literal">uv.lock</tt> as a toml file!</p>
<ul class="simple">
<li>My colleague updated his pre-commit installation and yes: <tt class="docutils literal">uv.lock</tt> was getting
re-formatted.</li>
<li>So: github actions had a newer version than we had.</li>
<li>Weird, as I just updated my python tool install this morning. Ah: I installed it with
homebrew instead of <tt class="docutils literal">uv tool</tt>, that's why it is still older.</li>
</ul>
<p>Anyway: small mystery solved.</p>
</div>

      ]]>
      </content>

    </entry>
  
    <entry>
      <title>Write the docs meetup: digital sovereignty for writers - Olufunke Moronfolu</title>
      <link rel="alternate" type="text/html"
            href="https://reinout.vanrees.org/weblog/2026/03/05/sovereignty.html" />
      <id>http://reinout.vanrees.org/weblog/2026/03/05/sovereignty.html</id>
      <!-- id is not https: prevents old entries from showing up again -->
      <author>
        <name>Reinout van Rees</name>
      </author>
      <published>2026-03-05T00:00:00+01:00</published>
      <updated>2026-03-10T09:07:00+01:00</updated>

      
        <category term="writethedocs" />
      
        <category term="python" />
      

      <content type="html"><![CDATA[
      <div class="document">
<p>(One of <a class="reference external" href="https://reinout.vanrees.org/weblog/2026/03/05/index.html">my summaries</a> of
the <a class="reference external" href="https://www.meetup.com/write-the-docs-amsterdam/events/313410641/">Amsterdam *write the docs* meetup</a>).</p>
<p>Full title: <em>digital sovereignty for writers: your data, your decisions</em>. Olufunke
Moronfolu has her website at <a class="reference external" href="https://writerwhocodes.com/">https://writerwhocodes.com/</a> .</p>
<p>&quot;Digital sovereignty is the ability to have control over your own digital destiny: the
data, hardware and software that you rely on and create&quot; (quote from the World Economic
Forum).</p>
<p>What do writers want? Mostly: to be read. For this you could for instance start looking
for (commercial) blogging platforms, searching for the best one. And after a while you
start looking for a different one. On and on. You can run into problems. Substack might
ban your newsletter. A google workspace domain being blocked. A Medium story getting
deleted without feedback.</p>
<p>Tim Berners-Lee intended for the web to be <strong>universal</strong> and <strong>open</strong>. But now it is
mostly a collection of isolated silos.</p>
<p>There are some questions you can ask yourself to test your sovereignty. If your current
platform deletes your account, is your content completely lost? Second question: can you
export your work in some portable format (like markdown).</p>
<p>If you are a technical writer, you have to do the test twice. Once for your own content
and once for your company's documentation.</p>
<p><strong>Own your content</strong>. Most sovereign for your own website/blog would be hugo/jekyll or
other static generators. In the middle are (self-hosted?) wordpress sites. Least
sovereign is something like linkedin/medium/substack. For company content,
confluence/notion would be least sovereign. Wiki.js/bookstack middle. The best is docs
as code like some markdown in git.</p>
<p>So: review the platform's policy. What is the ease of export? Do you have control?
What's the stability? Do you have an identity there? Perhaps even a domain?</p>
<p><strong>Own your identity</strong>. Having your own domain is best. If you're some-platform.com/name,
your identity goes away if the site disappears.</p>
<p><strong>Decide how to share</strong>. Sovereign would be an email list, an RSS feed or something like
the <a class="reference external" href="https://indieweb.org/POSSE">POSSE approach</a> (Publish (on your) Own Site,
Syndicate Elsewhere).</p>
<p><strong>Build for the future</strong>: build <em>something</em>. Start. It doesn't have to be perfect. Your
own domain name and a single static page is already much more sovereign than a million
followers on a site that could vanish tomorrow.</p>
<p>If you want to do more, join the &quot;independent web&quot; (indieweb, <a class="reference external" href="https://indieweb.org">https://indieweb.org</a>)
movement.</p>
<hr class="docutils" />
<p>Personal note: I've got my own domain. This is a blog entry that ends up in an RSS/atom
feed. The site is .rst files in a git repo. Statically generated with Sphinx. So: yeah,
pretty sovereign :-)</p>
</div>

      ]]>
      </content>

    </entry>
  
    <entry>
      <title>Write the docs meetup: developers documentation, your hidden strength - Frédéric Harper</title>
      <link rel="alternate" type="text/html"
            href="https://reinout.vanrees.org/weblog/2026/03/05/developer-docs.html" />
      <id>http://reinout.vanrees.org/weblog/2026/03/05/developer-docs.html</id>
      <!-- id is not https: prevents old entries from showing up again -->
      <author>
        <name>Reinout van Rees</name>
      </author>
      <published>2026-03-05T00:00:00+01:00</published>
      <updated>2026-03-05T18:22:00+01:00</updated>

      
        <category term="writethedocs" />
      
        <category term="python" />
      

      <content type="html"><![CDATA[
      <div class="document">
<p>(One of <a class="reference external" href="https://reinout.vanrees.org/weblog/2026/03/05/index.html">my summaries</a> of
the <a class="reference external" href="https://www.meetup.com/write-the-docs-amsterdam/events/313410641/">Amsterdam *write the docs* meetup</a>).</p>
<p>If you have a product, you need good developer documentation. &quot;It is an integral part of
your product: one cannot exist without the other&quot;. You might have the best product, but
if people don't know how to use it, it doesn't matter.</p>
<p>What he tells developers: good documentation reduces support tickets and angry
customers. You should be able to &quot;sell&quot; good documentation to your company: it saves
money and results in more sales.</p>
<p>Some notes on documentation contents:</p>
<ul class="simple">
<li>You need a search function. The first thing you need to add.</li>
<li>Think about John Snow (game of thrones): &quot;you know nothing, John Snow&quot;. Be detailed in
your instructions, they'll need it. Start with the assumption that the user knows
nothing about your program. Advanced users can easily skip those parts.</li>
<li>Have a proper architecture/structure. Simply having a &quot;home&quot; link to get back to the
start already helps. Add a &quot;getting started&quot; section with step-by-step instructions to
get something simple running.  And detailed how-to guides where you go into depth.</li>
<li>Show a table of contents of the current page.</li>
<li>Keep the docs of previous versions available.</li>
<li>Take great screenshots. Docs should have great quality and it especially shows in the
screenshots.</li>
<li>Don't show off your language skills too much. Keep the language simple. Not everyone
will have your documentation's language as their native language.</li>
<li>Test the code in your documentation! There's nothing more irritating than errors in
example code. And keep it up to date. Especially watch out when the software gets
updated. Do you give your documentation time to get updated?</li>
</ul>
<p>Some extra notes:</p>
<ul class="simple">
<li>Make your docs accessible for people with disabilities.</li>
<li>Are your docs fast? Load times help you get ranked higher in search engines.</li>
<li>Some people read your documentation on their phones: does it work there?</li>
<li>Try to make your docs open source. You might get an occasional fix. And perhaps more
feedback.</li>
</ul>
</div>

      ]]>
      </content>

    </entry>
  
    <entry>
      <title>Python Leiden meetup: PR vs ROC curves, which to use - Sultan K. Imangaliyev</title>
      <link rel="alternate" type="text/html"
            href="https://reinout.vanrees.org/weblog/2026/01/22/2-pr-roc-curves.html" />
      <id>http://reinout.vanrees.org/weblog/2026/01/22/2-pr-roc-curves.html</id>
      <!-- id is not https: prevents old entries from showing up again -->
      <author>
        <name>Reinout van Rees</name>
      </author>
      <published>2026-01-22T00:00:00+01:00</published>
      <updated>2026-01-22T20:57:00+01:00</updated>

      
        <category term="python" />
      
        <category term="pun" />
      

      <content type="html"><![CDATA[
      <div class="document">
<p>(One of <a class="reference external" href="https://reinout.vanrees.org/weblog/2026/01/22/index.html">my summaries</a> of
the <a class="reference external" href="https://pythonleiden.nl/">Python Leiden meetup</a> in Leiden, NL).</p>
<p>Precision-recall (PR) versus Receiver Operating Characteristics (ROC) curves: which one
to use if data is imbalanced?</p>
<p>Imbalanced data: for instance when you're investigating rare diseases. &quot;Rare&quot; means few
people have them. So if you have data, most of the data will be of healthy people,
there's a huge <strong>imbalance</strong> in the data.</p>
<p>Sensitivity versus specificity: sensitive means you find most of the sick people,
specificity means you want as few false negatives and false positives as possible.
Sensitivity/specificity looks a bit like precision/recall.</p>
<ul class="simple">
<li>Sensitivity: <em>true positive</em> rate.</li>
<li>Specificity: <em>false positive</em> rate</li>
</ul>
<p>If you classify, you can classify immediately into healthy/sick, but you can also use a
<em>probabilistic classifier</em> which returns a chance (percentage) that someone can be
classified as sick. You can then tweak which threshold you want to use: how sensitive
and/or specific do you want to be?</p>
<p>PR and ROC curves (curve = <em>graph</em> showing the sensitivity/specificity relation on two
axis) are two ways of measuring/visualising the sensitivity/specificity relation. He
showed some data: if the data is imbalanced, PR is much better at evaluating your model.
He compared balanced and imbalanced data with ROC and there was hardly a change in the
curve.</p>
<p>He used <a class="reference external" href="https://scikit-learn.org/">scikit-learn</a> for his data evaluations and demos.</p>
</div>

      ]]>
      </content>

    </entry>
  
    <entry>
      <title>Python Leiden meetup: PostgreSQL + Python in 2026 -- Aleksandr Dinu</title>
      <link rel="alternate" type="text/html"
            href="https://reinout.vanrees.org/weblog/2026/01/22/1-postgres-python-in-2026.html" />
      <id>http://reinout.vanrees.org/weblog/2026/01/22/1-postgres-python-in-2026.html</id>
      <!-- id is not https: prevents old entries from showing up again -->
      <author>
        <name>Reinout van Rees</name>
      </author>
      <published>2026-01-22T00:00:00+01:00</published>
      <updated>2026-01-22T20:13:00+01:00</updated>

      
        <category term="python" />
      
        <category term="pun" />
      

      <content type="html"><![CDATA[
      <div class="document">
<p>(One of <a class="reference external" href="https://reinout.vanrees.org/weblog/2026/01/22/index.html">my summaries</a> of
the <a class="reference external" href="https://pythonleiden.nl/">Python Leiden meetup</a> in Leiden, NL).</p>
<p>He's going to revisit common gotchas of Python ORM usage. Plus some Postgresql-specific tricks.</p>
<p>ORM (object relational mappers) define tables, columns etc using Python concepts:
classes, attributes and methods. In your software, you work with objects instead of
rows. They can help with database schema management (migrations and so). It looks like
this:</p>
<pre class="literal-block">
class Question(models.Model):
    question = models.Charfield(...)
    answer = models.Charfield(...)
</pre>
<p>You often have Python &quot;context managers&quot; for database sessions.</p>
<p>ORMs are handy, but you must be beware of what you're fetching:</p>
<pre class="literal-block">
# Bad, grabs all objects and then takes the length using python:
questions_count = len(Question.objects.all())
# Good: let the database do it,
# the code does the equivalent of &quot;SELECT COUNT(*)&quot;:
questions_count = Question.objects.all().count()
</pre>
<p>Relational databases allow 1:M and N:M relations. You use them with <tt class="docutils literal">JOIN</tt> in SQL. If
you use an ORM, make sure you use the database to follow the relations. If you first
grab the first set of objects and then grab the second kind of objects with python, your
code will be much slower.</p>
<p>&quot;Migrations&quot; generated by your ORM to move from one version of your schema to the next
are real handy. But <strong>not all SQL concepts can be expressed in an ORM</strong>. Custom types,
stored procedures. You have to handle them yourselves. You can get undesired behaviour
as specific database versions can take a long time rebuilding after a change.</p>
<p>Migrations are nice, but they can lead to other problems from a database maintainer's
point of view, like the performance suddenly dropping. And optimising is hard as often
you don't know which server is connecting how much and also you don't know what is
queried. Some solutions for postgresql:</p>
<ul class="simple">
<li><tt class="docutils literal">log_line_prefix = '%a %u %d&quot;</tt> to show who is connecting to which database.</li>
<li><tt class="docutils literal">log_min_duration_statement = 1000</tt> logs every query taking more than 1000ms.</li>
<li><tt class="docutils literal">log_lock_waits = on</tt> for feedback on blocking operations (like migrations).</li>
<li>Handy: feedback on the number of queries being done, as simple programming errors can
translate into lots of small queries instead of one faster bigger one.</li>
</ul>
<p>If you've found a slow query, run that query with <tt class="docutils literal">EXPLAIN (ANALYZE, BUFFERS)
<span class="pre">the-query</span></tt>. <tt class="docutils literal">BUFFERS</tt> tells you how many pages of 8k the server uses for your query
(and whether those were memory or disk pages). This is <em>so</em> useful that they made it the
default in postgresql 18.</p>
<p>Some tools:</p>
<ul class="simple">
<li><a class="reference external" href="https://github.com/dimitri/regresql">RegreSQL</a>: performance regression testing. You
feed it a list of queries that you worry about. It will store how those queries are
executed and compare it with the new version of your code and warn you when one of
those queries suddenly takes a lot more time.</li>
<li><a class="reference external" href="https://squawkhq.com/">Squawk</a>: tells you (in CI, like github actions) which
migrations are backward-incompatible or that might take a long time.</li>
<li>You can look at one of the branching tools: aimed at getting access to production
databases for testing. Like running your migration against a &quot;branch&quot;/copy of
production. There are several tricks that are used, like filesystem layers.
&quot;pg_branch&quot; and &quot;pgcow&quot; are examples. Several DB-as-a-service products also provide
it (Databricks Lakebase, Neon, Heroku, Postgres.ai).</li>
</ul>
</div>

      ]]>
      </content>

    </entry>
  
    <entry>
      <title>Ansible-lint pre-commit problem + "fix"</title>
      <link rel="alternate" type="text/html"
            href="https://reinout.vanrees.org/weblog/2025/11/19/ansible-lint-pre-commit.html" />
      <id>http://reinout.vanrees.org/weblog/2025/11/19/ansible-lint-pre-commit.html</id>
      <!-- id is not https: prevents old entries from showing up again -->
      <author>
        <name>Reinout van Rees</name>
      </author>
      <published>2025-11-19T00:00:00+01:00</published>
      <updated>2025-11-19T21:17:00+01:00</updated>

      
        <category term="python" />
      
        <category term="django" />
      

      <content type="html"><![CDATA[
      <div class="document">
<p>I'm used to running <tt class="docutils literal"><span class="pre">pre-commit</span> autoupdate</tt> regularly to update the versions of the
linters/formatters that I use. Especially when there's some error.</p>
<p>For example, a couple of months ago, there was some problem with ansible-lint.
You have an <tt class="docutils literal"><span class="pre">ansible-lint</span></tt>, <tt class="docutils literal">ansible</tt> and <tt class="docutils literal"><span class="pre">ansible-core</span></tt> package and one of them
needed an upgrade. I'd get an error like this:</p>
<pre class="literal-block">
ModuleNotFoundError: No module named 'ansible.parsing.yaml.constructor'
</pre>
<p>The solution: <tt class="docutils literal"><span class="pre">pre-commit</span> autoupdate</tt>, which grabbed a new ansible-lint version
that solved the problem. <strong>Upgrading is good</strong>.</p>
<p>But... little over a month ago, <a class="reference external" href="https://github.com/ansible/ansible-lint/pull/4796/files">ansible-lint pinned python to 3.13</a> in the pre-commit hook. So
when you update, you suddenly need to have 3.13 on your machine. I have that locally,
but on the often-used &quot;ubuntu latest&quot; (24.04) github action runner, only 3.12 is
installed by default. Then you'd get this:</p>
<pre class="literal-block">
[INFO] Installing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/astral-sh/ruff-pre-commit.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/ansible-community/ansible-lint.git.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
An unexpected error has occurred: CalledProcessError: command:
  ('/opt/hostedtoolcache/Python/3.12.12/x64/bin/python', '-mvirtualenv',
  '/home/runner/.cache/pre-commit/repomm4m0yuo/py_env-python3.13', '-p', 'python3.13')
return code: 1
stdout:
    RuntimeError: failed to find interpreter for Builtin discover of python_spec='python3.13'
stderr: (none)
Check the log at /home/runner/.cache/pre-commit/pre-commit.log
Error: Process completed with exit code 3.
</pre>
<p>Ansible-lint's pre-commit hook needs 3.10+ or so, but won't accept anything except 3.13.
Here's the change: <a class="reference external" href="https://github.com/ansible/ansible-lint/pull/4796">https://github.com/ansible/ansible-lint/pull/4796</a> (including some
comments that it is not ideal, including the github action problem).</p>
<p>The change apparently gives a good error message to people running too-old python
versions, but it punishes those that do regular updates (and have perfectly fine
non-3.13 python versions). A similar pin was done in &quot;black&quot; and later <a class="reference external" href="https://github.com/psf/black/pull/2430">reverted (see
the comments on this issue)</a> as it caused too
many problems.</p>
<p>Note: <a class="reference external" href="https://github.com/ansible/ansible-lint/issues/4812#issuecomment-3381284183">this comment</a> gives
some of the reasons for hardcoding 3.13. Pre-commit itself doesn't have a way to specify
a minimum Python version. Apparently old Python version cans lead to weird install
errors, though I haven't found a good ticket about that in the issue tracker. The number
of issues in the tracker is impressively high, so I <em>can</em> imagine such a hardcoded
version helping a bit.</p>
<p>Now on to the <strong>&quot;fix&quot;</strong>. Override the <tt class="docutils literal">language_version</tt> like this:</p>
<pre class="literal-block">
- repo: https://github.com/ansible-community/ansible-lint.git
  hooks:
    - id: ansible-lint
      language_version: python3  # or python3.12 or so
</pre>
<p>If you use ansible-lint a lot (like I do), you'll have to add that line to all your
(django) project repositories when you update your pre-commit config...</p>
<p>I personally think this pinning is a bad idea. After <a class="reference external" href="https://github.com/ansible/ansible-lint/issues/4812">some discussion in issue 4821</a> I created a sub-optimal proposal
to <a class="reference external" href="https://github.com/ansible/ansible-lint/issues/4819">at least setting the default to 3.12</a>, but that issue was
closed&amp;locked because I apparently &quot;didn't search the issue tracker&quot;.</p>
<p>Anyway, this blog post hopefully helps people adjust their many pre-commit configs.</p>
</div>

      ]]>
      </content>

    </entry>
  
    <entry>
      <title>Python Leiden (NL) meetup summaries</title>
      <link rel="alternate" type="text/html"
            href="https://reinout.vanrees.org/weblog/2025/11/13/python-leiden.html" />
      <id>http://reinout.vanrees.org/weblog/2025/11/13/python-leiden.html</id>
      <!-- id is not https: prevents old entries from showing up again -->
      <author>
        <name>Reinout van Rees</name>
      </author>
      <published>2025-11-13T00:00:00+01:00</published>
      <updated>2025-11-14T10:38:00+01:00</updated>

      
        <category term="python" />
      
        <category term="pun" />
      

      <content type="html"><![CDATA[
      <div class="document">
<p>My summaries from the <a class="reference external" href="https://www.meetup.com/python-leiden-user-group/events/310885151/">sixth Python meetup in Leiden (NL)</a>.</p>
<div class="section" id="python-and-mongodb-a-perfect-marriage-mathijs-gaastra">
<h1>Python and MongoDB, a perfect marriage - Mathijs Gaastra</h1>
<p>His first experience with Mongodb was when he had to build a <em>patient data warehouse</em>
based on literature. He started with postgres, but the fixed table structure was very
limiting. Mongodb was much more flexible.</p>
<p>Postgres is a <em>relational database</em>, Mongodb is a <em>document database</em>. Relational:
tables, clearly defined relationships and a pre-defined structure. Document/nosql:
documents, flexible relationships and a flexible structure.</p>
<p>Nosql/document databases can scale horizontally. Multiple servers, connected. Relational
databases have different scaling mechanisms.</p>
<p>Why is mongo such a nice combination with python?</p>
<ul class="simple">
<li>The PyMongo package is great and has a simple syntax.</li>
<li>It is easily scalable</li>
<li>Documents are in BSON format (&quot;binary json&quot;) which is simple to use and pretty
efficient.</li>
</ul>
<p>He showed example python code, comparing a mysql example with a Mongodb version. The
Mongodb version did indeed look simpler.</p>
<p>The advantage of Mongodb (the freedom) also is its drawback: you need to do your own
validation and your own housekeeping, otherwise your data slowly becomes unusable.</p>
<p>Mathijs is now only using Mongodb, mostly because of the speed of development he enjoys
with it.</p>
</div>
<div class="section" id="identifying-blast-beats-in-music-using-python-lino-mediavilla">
<h1>Identifying “blast beats” in music using Python - Lino Mediavilla</h1>
<p>He showed a couple of videos of drummers. Some with and some without &quot;blast beats&quot;. In
metal (<em>if I understood correctly</em>) it means both a lot of base drum, but essentially
also a &quot;machine gun&quot; on tne snare drum. He likes this kind of music a lot, so he wanted
to analize it programmatically</p>
<p>He used the <a class="reference external" href="https://github.com/adefossez/demucs">demucs</a> library for his <a class="reference external" href="https://github.com/MutilatedPeripherals/blastbeat-counter">blast beat
counter</a> project. Demucs
separates different instruments out of a piece of music.</p>
<p>With fourier transforms, he could analyse the frequencies. Individual drum sounds (snare
drum hit, base drum hit) were analysed this way.</p>
<p>With the analysed frequency bits, they could recognise them in a piece of music and
count occurrences and pick out the blast beats. He had some nice visualisations, too.</p>
<p>He was asked to analyze &quot;never gonna give you up&quot; from Rick Ashley :-) Downloading it
from youtube, separating out the drums, ananlysing it, visualising it: <strong>it worked</strong>!
Nice: live demo. (Of course there were no blast beats in the song.)</p>
</div>
<div class="section" id="deploying-python-apps-on-your-own-infra-with-github-actions-michiel-beijen">
<h1>Deploying Python apps on your own infra with Github actions - Michiel Beijen</h1>
<p>Live demo time again! He build a quick jekyll site (static site generator) and he's got
a small <a class="reference external" href="https://hetzner.com">hetzner</a> server. Just a bit of apache config and he's
got an empty directory that's being hosted on a domainname. He quickly did this by hand.</p>
<p>Next he added his simple code to a git repo and uploaded it to github.</p>
<p>A nice trick for Github actions are <strong>self hosted runners</strong>. They're easy to install,
just follow the instructions on Github.</p>
<p>The runner can then run what's in your github's action, like &quot;generate files with jekyll
and store them in the right local folder on the server&quot;.</p>
<p>The runner runs on <em>your</em> server, running <em>your</em> code: a much nicer solution than giving
your ssh key to Github and having it log into your server. You also can use it on some
local computer without an external address: the runner will <em>poll</em> Github instead of it
being Github that <em>sends</em> you messages.</p>
<p>The auto-deploy worked. And while he was busy with his demo, two PRs with changes to the
static website had already been created by other participants. He merged them and the
site was indeed updated right away.</p>
</div>
</div>

      ]]>
      </content>

    </entry>
  

</feed>