<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2026-03-30T18:47:32+00:00</updated><id>/feed.xml</id><title type="html">Borutzki</title><subtitle>Coding for people, not just processors. Pragmatic solutions for non-trivial problems.</subtitle><author><name>Kacper Borucki</name></author><entry><title type="html">How to assert exception message in PyTest?</title><link href="/2026/03/30/how-to-assert-exception-message-in-pytest.html" rel="alternate" type="text/html" title="How to assert exception message in PyTest?" /><published>2026-03-30T00:00:00+00:00</published><updated>2026-03-30T00:00:00+00:00</updated><id>/2026/03/30/how-to-assert-exception-message-in-pytest</id><content type="html" xml:base="/2026/03/30/how-to-assert-exception-message-in-pytest.html"><![CDATA[<p>I kept forgetting how to assert exception messages in PyTest, so I finally checked <a href="https://docs.pytest.org/en/stable/reference/reference.html#pytest-raises">the docs</a>. Here’s a reference snippet.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">pytest</span>

<span class="k">def</span> <span class="nf">broken</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
    <span class="k">raise</span> <span class="nb">ValueError</span><span class="p">(</span><span class="s">"something went wrong"</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">test_broken</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
    <span class="k">with</span> <span class="n">pytest</span><span class="p">.</span><span class="n">raises</span><span class="p">(</span><span class="nb">ValueError</span><span class="p">,</span> <span class="n">match</span><span class="o">=</span><span class="s">"went wrong"</span><span class="p">):</span>
        <span class="n">broken</span><span class="p">()</span>
</code></pre></div></div>

<blockquote>
  <p><em>That’s it for today. Happy hacking! 🐍</em></p>
</blockquote>]]></content><author><name>Kacper Borucki</name></author><summary type="html"><![CDATA[I kept forgetting how to assert exception messages in PyTest, so I finally checked the docs. Here’s a reference snippet. import pytest def broken() -&gt; None: raise ValueError("something went wrong") def test_broken() -&gt; None: with pytest.raises(ValueError, match="went wrong"): broken() That’s it for today. Happy hacking! 🐍]]></summary></entry><entry><title type="html">How to use overloaded signatures in Python?</title><link href="/2026/02/07/how-to-use-overloaded-signatures-in-python.html" rel="alternate" type="text/html" title="How to use overloaded signatures in Python?" /><published>2026-02-07T00:00:00+00:00</published><updated>2026-02-07T00:00:00+00:00</updated><id>/2026/02/07/how-to-use-overloaded-signatures-in-python</id><content type="html" xml:base="/2026/02/07/how-to-use-overloaded-signatures-in-python.html"><![CDATA[<p>Sometimes a function takes multiple arguments of different types, and the return type depends on specific combinations of inputs. It’s often easy to understand by reading code, but how do you tell the type checker that this is the case? This is where the <code class="language-plaintext highlighter-rouge">@overload</code> decorator from the <code class="language-plaintext highlighter-rouge">typing</code> module comes handy.</p>

<!--more-->

<h2 id="but-whats-the-problem-to-solve">But what’s the problem to solve?</h2>

<p>Let’s take the function below as an example.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">generate_config</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nb">dict</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="n">Action</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span> <span class="o">|</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
    <span class="n">config</span> <span class="o">=</span> <span class="n">build_from_context</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">DEFAULT_TEMPLATE</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">action</span> <span class="o">==</span> <span class="n">Action</span><span class="p">.</span><span class="n">DELETE</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">build_list_of_xpaths</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>  <span class="c1"># &lt;- returns list[str]
</span>
    <span class="k">return</span> <span class="n">config</span>  <span class="c1"># &lt;- returns dict
</span></code></pre></div></div>

<p>It takes a template context and depending on <code class="language-plaintext highlighter-rouge">Action</code>, builds either <code class="language-plaintext highlighter-rouge">config</code> dictionary<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">1</a></sup> or a list of XPaths to be removed from some other config. The code here is pretty straightforward and let’s assume it cannot be split into two functions because “some legacy dependencies”.</p>

<p>Now let’s say we need two proxy functions to generate specific types of configs somewhere else, like below.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">function_that_needs_delete_config</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
    <span class="k">return</span> <span class="n">generate_config</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="n">Action</span><span class="p">.</span><span class="n">DELETE</span><span class="p">)</span>


<span class="k">def</span> <span class="nf">function_that_needs_modify_config</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
    <span class="k">return</span> <span class="n">generate_config</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="n">Action</span><span class="p">.</span><span class="n">MODIFY</span><span class="p">)</span>
</code></pre></div></div>

<p>Given the implementation of <code class="language-plaintext highlighter-rouge">generate_config</code>, we know that type hints are correct here. But let’s see what happens if we run <code class="language-plaintext highlighter-rouge">mypy</code> on this code.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  code_samples git:<span class="o">(</span>main<span class="o">)</span> ✗ uv run mypy /Users/borutzki/Dev/code_samples/python/overload.py
python/overload.py:39: error: Incompatible <span class="k">return </span>value <span class="nb">type</span> <span class="o">(</span>got <span class="s2">"dict[Any, Any] | list[str]"</span>, expected <span class="s2">"list[str]"</span><span class="o">)</span>  <span class="o">[</span><span class="k">return</span><span class="nt">-value</span><span class="o">]</span>
python/overload.py:43: error: Incompatible <span class="k">return </span>value <span class="nb">type</span> <span class="o">(</span>got <span class="s2">"dict[Any, Any] | list[str]"</span>, expected <span class="s2">"dict[Any, Any]"</span><span class="o">)</span>  <span class="o">[</span><span class="k">return</span><span class="nt">-value</span><span class="o">]</span>
</code></pre></div></div>

<p>Yep, <code class="language-plaintext highlighter-rouge">mypy</code> sees two problems:</p>

<ul>
  <li>in <code class="language-plaintext highlighter-rouge">function_that_needs_delete_config</code> it expects <code class="language-plaintext highlighter-rouge">list[str]</code> as return type, but <code class="language-plaintext highlighter-rouge">generate_config</code> can return either this or <code class="language-plaintext highlighter-rouge">dict</code>,</li>
  <li>in <code class="language-plaintext highlighter-rouge">function_that_needs_modify_config</code> it expects <code class="language-plaintext highlighter-rouge">dict</code> as return type, but <code class="language-plaintext highlighter-rouge">generate_config</code> can return either this or <code class="language-plaintext highlighter-rouge">list[str]</code>.</li>
</ul>

<p>But I want to have neat type hints, and I know the issue doesn’t actually exist, right? If only I could tell this stupid type checker what I know…</p>

<h2 id="overloading-function-signature"><code class="language-plaintext highlighter-rouge">@overload</code>ing function signature</h2>

<p>Thankfully, there’s the <code class="language-plaintext highlighter-rouge">@overload</code><sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">2</a></sup> decorator in Python’s <code class="language-plaintext highlighter-rouge">typing</code> module. Below is how to use it.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">overload</span><span class="p">,</span> <span class="n">Literal</span><span class="p">,</span> <span class="n">Any</span>

<span class="c1"># ... code omitted
</span>
<span class="o">@</span><span class="n">overload</span>
<span class="k">def</span> <span class="nf">generate_config</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nb">dict</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="n">Literal</span><span class="p">[</span><span class="n">Action</span><span class="p">.</span><span class="n">MODIFY</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span> <span class="p">...</span>
<span class="o">@</span><span class="n">overload</span>
<span class="k">def</span> <span class="nf">generate_config</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nb">dict</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="n">Literal</span><span class="p">[</span><span class="n">Action</span><span class="p">.</span><span class="n">DELETE</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span> <span class="p">...</span>
<span class="k">def</span> <span class="nf">generate_config</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nb">dict</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="n">Action</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span> <span class="o">|</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
    <span class="n">config</span> <span class="o">=</span> <span class="n">build_from_context</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">DEFAULT_TEMPLATE</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">action</span> <span class="o">==</span> <span class="n">Action</span><span class="p">.</span><span class="n">DELETE</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">build_list_of_xpaths</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>  <span class="c1"># &lt;- returns list[str]
</span>
    <span class="k">return</span> <span class="n">config</span>
</code></pre></div></div>

<p>What did I do? Created two additional signatures for the same function. One specifies result type for <code class="language-plaintext highlighter-rouge">MODIFY</code> action, the other - for <code class="language-plaintext highlighter-rouge">DELETE</code> action. Let’s see output from <code class="language-plaintext highlighter-rouge">mypy</code> now.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  code_samples git:<span class="o">(</span>main<span class="o">)</span> ✗ uv run mypy /Users/borutzki/Dev/code_samples/python/overload.py
Success: no issues found <span class="k">in </span>1 <span class="nb">source </span>file
</code></pre></div></div>

<p>There’s one catch here, though. Once you define at least one <code class="language-plaintext highlighter-rouge">@overload</code>, all valid call patterns must be described by overloads, because the implementation signature is ignored by type checkers. Otherwise, <code class="language-plaintext highlighter-rouge">mypy</code> will complain again.</p>

<p>As an example, let’s add another proxy function, this time for <code class="language-plaintext highlighter-rouge">ADD</code>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">function_that_needs_add_config</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
    <span class="k">return</span> <span class="n">generate_config</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="n">Action</span><span class="p">.</span><span class="n">ADD</span><span class="p">)</span>
</code></pre></div></div>

<p>Now, when I run <code class="language-plaintext highlighter-rouge">mypy</code> again, I get the following output:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  code_samples git:<span class="o">(</span>main<span class="o">)</span> ✗ uv run mypy /Users/borutzki/Dev/code_samples/python/overload.py
python/overload.py:47: error: No overload variant of <span class="s2">"generate_config"</span> matches argument types <span class="s2">"dict[Any, Any]"</span>, <span class="s2">"Action"</span>  <span class="o">[</span>call-overload]
python/overload.py:47: note: Possible overload variants:
python/overload.py:47: note:     def generate_config<span class="o">(</span>context: dict[Any, Any], action: Literal[Action.MODIFY]<span class="o">)</span> -&gt; dict[Any, Any]
python/overload.py:47: note:     def generate_config<span class="o">(</span>context: dict[Any, Any], action: Literal[Action.DELETE]<span class="o">)</span> -&gt; list[str]
</code></pre></div></div>

<p>which is a bit confusing, but points to a simple fact - that I am missing an overload variant for <code class="language-plaintext highlighter-rouge">Action.ADD</code>. I can add it, like below.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">overload</span>
<span class="k">def</span> <span class="nf">generate_config</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nb">dict</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="n">Literal</span><span class="p">[</span><span class="n">Action</span><span class="p">.</span><span class="n">ADD</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span> <span class="p">...</span>
<span class="o">@</span><span class="n">overload</span>
<span class="k">def</span> <span class="nf">generate_config</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nb">dict</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="n">Literal</span><span class="p">[</span><span class="n">Action</span><span class="p">.</span><span class="n">MODIFY</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span> <span class="p">...</span>
<span class="o">@</span><span class="n">overload</span>
<span class="k">def</span> <span class="nf">generate_config</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nb">dict</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="n">Literal</span><span class="p">[</span><span class="n">Action</span><span class="p">.</span><span class="n">DELETE</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span> <span class="p">...</span>
<span class="k">def</span> <span class="nf">generate_config</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nb">dict</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="n">Action</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span> <span class="o">|</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
    <span class="n">config</span> <span class="o">=</span> <span class="n">build_from_context</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">DEFAULT_TEMPLATE</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">action</span> <span class="o">==</span> <span class="n">Action</span><span class="p">.</span><span class="n">DELETE</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">build_list_of_xpaths</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>  <span class="c1"># &lt;- returns list[str]
</span>
    <span class="k">return</span> <span class="n">config</span>  <span class="c1"># &lt;- returns dict
</span></code></pre></div></div>

<p>Afterwards, <code class="language-plaintext highlighter-rouge">mypy</code> once again has no complaints about my code.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  code_samples git:<span class="o">(</span>main<span class="o">)</span> ✗ uv run mypy /Users/borutzki/Dev/code_samples/python/overload.py
Success: no issues found <span class="k">in </span>1 <span class="nb">source </span>file
</code></pre></div></div>

<p>Since <code class="language-plaintext highlighter-rouge">Action.MODIFY</code> and <code class="language-plaintext highlighter-rouge">Action.ADD</code> will return the same type, I can reduce them to a single <code class="language-plaintext highlighter-rouge">@overload</code>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">overload</span>
<span class="k">def</span> <span class="nf">generate_config</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nb">dict</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="n">Literal</span><span class="p">[</span><span class="n">Action</span><span class="p">.</span><span class="n">ADD</span><span class="p">,</span> <span class="n">Action</span><span class="p">.</span><span class="n">MODIFY</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span> <span class="p">...</span>
<span class="o">@</span><span class="n">overload</span>
<span class="k">def</span> <span class="nf">generate_config</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nb">dict</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="n">Literal</span><span class="p">[</span><span class="n">Action</span><span class="p">.</span><span class="n">DELETE</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span> <span class="p">...</span>
<span class="k">def</span> <span class="nf">generate_config</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nb">dict</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="n">Action</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span> <span class="o">|</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
    <span class="n">config</span> <span class="o">=</span> <span class="n">build_from_context</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">DEFAULT_TEMPLATE</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">action</span> <span class="o">==</span> <span class="n">Action</span><span class="p">.</span><span class="n">DELETE</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">build_list_of_xpaths</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>  <span class="c1"># &lt;- returns list[str]
</span>
    <span class="k">return</span> <span class="n">config</span>  <span class="c1"># &lt;- returns dict
</span></code></pre></div></div>

<h2 id="couldnt-it-be-done-in-some-other-way">Couldn’t it be done in some other way?</h2>

<p>Let me analyse some other solutions that didn’t solve the problem described here.</p>

<h3 id="split-the-function-into-two">Split the function into two</h3>

<p>This would work if it was feasible in the code, and in the code from which the example emerged, it was not. The “legacy dependency” was real.</p>

<h3 id="functoolssingledispatch"><code class="language-plaintext highlighter-rouge">@functools.singledispatch</code></h3>

<p>I thought about using <code class="language-plaintext highlighter-rouge">@singledispatch</code><sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">3</a></sup> decorator for the function, but it has two limitations that make it infeasible.</p>

<p>First, it can only dispatch based on the <em>type</em> of first argument - so I would have to refactor all non-keyword-argument calls to the function. But even putting that aside, single-dispatch does not work with values - and in this case, <code class="language-plaintext highlighter-rouge">Action</code> is an enum, so its type is the same for all the arguments. Only value changes in signatures.</p>

<p>Technically, these problems could be solved by defining different type (class) for each action, but would it be really that readable?</p>

<h3 id="disable-type-checking">Disable type checking</h3>

<p>It’s tempting to disable type checker for this specific case, but even having all obvious downsides of it put aside, every call to the function would have to ignore the return type. And I would lose the information about incorrect expected return types. And IDE hints. Not nice.</p>

<h2 id="summary">Summary</h2>

<p>Maybe there are more approaches to the type-checking issue, but <code class="language-plaintext highlighter-rouge">@overload</code> was perfectly suited for the job. Hopefully, now you know how it can be used in similar cases in your own code.</p>

<blockquote>
  <p><em>That’s it for today. Happy hacking! 🐍</em></p>
</blockquote>

<hr />

<p><br /></p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:3" role="doc-endnote">
      <p>For brevity, I don’t use specific type hints for the dict type. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:1" role="doc-endnote">
      <p><a href="https://typing.python.org/en/latest/spec/overload.html#overload-definitions">https://typing.python.org/en/latest/spec/overload.html#overload-definitions</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p><a href="https://docs.python.org/3/library/functools.html#functools.singledispatch">https://docs.python.org/3/library/functools.html#functools.singledispatch</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Kacper Borucki</name></author><summary type="html"><![CDATA[Sometimes a function takes multiple arguments of different types, and the return type depends on specific combinations of inputs. It’s often easy to understand by reading code, but how do you tell the type checker that this is the case? This is where the @overload decorator from the typing module comes handy.]]></summary></entry><entry><title type="html">How to dump Django ORM data to JSON while debugging?</title><link href="/2026/01/25/how-to-dump-django-orm-data-to-json-while-debugging.html" rel="alternate" type="text/html" title="How to dump Django ORM data to JSON while debugging?" /><published>2026-01-25T00:00:00+00:00</published><updated>2026-01-25T00:00:00+00:00</updated><id>/2026/01/25/how-to-dump-django-orm-data-to-json-while-debugging</id><content type="html" xml:base="/2026/01/25/how-to-dump-django-orm-data-to-json-while-debugging.html"><![CDATA[<p>Sometimes, I need to debug specific high-level tests by inspecting what gets created in the database as a side effect. I could use a debugger and poke around the Django ORM at a breakpoint - but quite often it’s simply faster to dump the entire table to JSON, see what’s there, and then apply fixes accordingly.</p>

<!--more-->

<p>Normally, to do that, <code class="language-plaintext highlighter-rouge">manage.py dumpdata</code> could be used. But since tests are ephemeral and don’t necessarily preserve the database after they finish, a more scripted approach is often more convenient. This is where <code class="language-plaintext highlighter-rouge">serializers.serialize</code> comes in handy.</p>

<p>Here’s how to use it:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">django.core</span> <span class="kn">import</span> <span class="n">serializers</span>
<span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">Service</span>

<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"dump.json"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">out</span><span class="p">:</span>
    <span class="n">data</span> <span class="o">=</span> <span class="n">serializers</span><span class="p">.</span><span class="n">serialize</span><span class="p">(</span><span class="s">"json"</span><span class="p">,</span> <span class="n">Service</span><span class="p">.</span><span class="n">objects</span><span class="p">.</span><span class="nb">all</span><span class="p">())</span>
    <span class="n">out</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</code></pre></div></div>

<p>and the result for my model is as shown below (auto-formatted for readability).</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nl">"model"</span><span class="p">:</span><span class="w"> </span><span class="s2">"blog.service"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"pk"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
        </span><span class="nl">"fields"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"SERVICE_1"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"service_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"L3Connection"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-01-25T11:17:48.880Z"</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nl">"model"</span><span class="p">:</span><span class="w"> </span><span class="s2">"blog.service"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"pk"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
        </span><span class="nl">"fields"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"SERVICE_2"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"service_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"L3Connection"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-01-25T11:17:48.880Z"</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>

<p>This approach is neat because it doesn’t prevent me from narrowing down the data. If I’m only interested in a subset of records, I can simply replace <code class="language-plaintext highlighter-rouge">all()</code> with <code class="language-plaintext highlighter-rouge">filter()</code> and dump exactly what I need.</p>

<p>To try it yourself inside a <code class="language-plaintext highlighter-rouge">TestCase</code>, feel free to copy-paste the snippet below:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">DebugWithDumpTests</span><span class="p">(</span><span class="n">TestCase</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">test_dump_to_json</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
        <span class="n">_</span> <span class="o">=</span> <span class="n">Service</span><span class="p">.</span><span class="n">objects</span><span class="p">.</span><span class="n">create</span><span class="p">(</span><span class="n">service_type</span><span class="o">=</span><span class="s">"L3Connection"</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s">"SERVICE_1"</span><span class="p">)</span>
        <span class="n">_</span> <span class="o">=</span> <span class="n">Service</span><span class="p">.</span><span class="n">objects</span><span class="p">.</span><span class="n">create</span><span class="p">(</span><span class="n">service_type</span><span class="o">=</span><span class="s">"L3Connection"</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s">"SERVICE_2"</span><span class="p">)</span>

        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"dump.json"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">out</span><span class="p">:</span>
            <span class="n">data</span> <span class="o">=</span> <span class="n">serializers</span><span class="p">.</span><span class="n">serialize</span><span class="p">(</span><span class="s">"json"</span><span class="p">,</span> <span class="n">Service</span><span class="p">.</span><span class="n">objects</span><span class="p">.</span><span class="nb">all</span><span class="p">())</span>
            <span class="n">out</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</code></pre></div></div>

<p>but remember not to commit the database dump to your repository!</p>

<blockquote>
  <p><em>That’s it for today. Happy hacking! 🐍</em></p>
</blockquote>]]></content><author><name>Kacper Borucki</name></author><summary type="html"><![CDATA[Sometimes, I need to debug specific high-level tests by inspecting what gets created in the database as a side effect. I could use a debugger and poke around the Django ORM at a breakpoint - but quite often it’s simply faster to dump the entire table to JSON, see what’s there, and then apply fixes accordingly.]]></summary></entry><entry><title type="html">Why using [n] on a Django QuerySet can be unsafe?</title><link href="/2026/01/19/why-using-n-on-a-django-queryset-can-be-unsafe.html" rel="alternate" type="text/html" title="Why using [n] on a Django QuerySet can be unsafe?" /><published>2026-01-19T00:00:00+00:00</published><updated>2026-01-19T00:00:00+00:00</updated><id>/2026/01/19/why-using-n-on-a-django-queryset-can-be-unsafe</id><content type="html" xml:base="/2026/01/19/why-using-n-on-a-django-queryset-can-be-unsafe.html"><![CDATA[<p>What if I told you that the following way of taking the second object from a Django <code class="language-plaintext highlighter-rouge">QuerySet</code> might be unreliable and can lead to non-deterministic failures under some circumstances?</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">second_service</span> <span class="o">=</span> <span class="n">Service</span><span class="p">.</span><span class="n">objects</span><span class="p">.</span><span class="nb">filter</span><span class="p">(</span><span class="n">service_type</span><span class="o">=</span><span class="s">"L3Connection"</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span>
</code></pre></div></div>

<!--more-->

<p>Let me dig a bit into the topic in this brief note.</p>

<ul id="markdown-toc">
  <li><a href="#backstory" id="markdown-toc-backstory">Backstory</a>    <ul>
      <li><a href="#some-info-about-setup" id="markdown-toc-some-info-about-setup">Some info about setup</a></li>
      <li><a href="#the-problem" id="markdown-toc-the-problem">The problem</a></li>
    </ul>
  </li>
  <li><a href="#how-does-filter-work" id="markdown-toc-how-does-filter-work">How does <code class="language-plaintext highlighter-rouge">.filter()</code> work?</a></li>
  <li><a href="#how-does-first-pick-the-first-element-from-the-query" id="markdown-toc-how-does-first-pick-the-first-element-from-the-query">How does <code class="language-plaintext highlighter-rouge">first()</code> pick the first element from the query?</a></li>
  <li><a href="#what-can-be-done-about-it" id="markdown-toc-what-can-be-done-about-it">What can be done about it?</a>    <ul>
      <li><a href="#make-the-query-stricter" id="markdown-toc-make-the-query-stricter">Make the query stricter</a></li>
      <li><a href="#chain-order_by-with--filter" id="markdown-toc-chain-order_by-with--filter">Chain <code class="language-plaintext highlighter-rouge">order_by</code> with  <code class="language-plaintext highlighter-rouge">filter</code></a></li>
      <li><a href="#specify-ordering-in-models-meta" id="markdown-toc-specify-ordering-in-models-meta">Specify <code class="language-plaintext highlighter-rouge">ordering</code> in model’s <code class="language-plaintext highlighter-rouge">Meta</code></a></li>
    </ul>
  </li>
  <li><a href="#summary" id="markdown-toc-summary">Summary</a></li>
</ul>

<h2 id="backstory">Backstory</h2>

<h3 id="some-info-about-setup">Some info about setup</h3>

<p>On a daily basis I work on a Django app based off Netbox. The app uses PostgreSQL instance as a database service (or container with it for testing purposes). The database schema is pretty complex, and the workflows I develop have multiple side effects impacting multiple database rows.</p>

<h3 id="the-problem">The problem</h3>

<p>So, the other day I was debugging one unit test, that was randomly failing in my project’s CI pipeline. The culprit was the fact that the following line of code:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">second_service</span> <span class="o">=</span> <span class="n">Service</span><span class="p">.</span><span class="n">objects</span><span class="p">.</span><span class="nb">filter</span><span class="p">(</span><span class="n">service_type</span><span class="o">=</span><span class="s">"L3Connection"</span><span class="p">).</span><span class="n">first</span><span class="p">()</span>
</code></pre></div></div>

<p>was changed to this one:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">second_service</span> <span class="o">=</span> <span class="n">Service</span><span class="p">.</span><span class="n">objects</span><span class="p">.</span><span class="nb">filter</span><span class="p">(</span><span class="n">service_type</span><span class="o">=</span><span class="s">"L3Connection"</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span>
</code></pre></div></div>

<p>because code behaviour changed.</p>

<p>The query aimed to retrieve an object created during the workflow. Two objects of the same type existed, but only one mattered for the test. Before the change, it was the first object; after, it was the second.</p>

<p>After the change in test code, unit tests started failing approximately 50% of the time.</p>

<p>But why?</p>

<h2 id="how-does-filter-work">How does <code class="language-plaintext highlighter-rouge">.filter()</code> work?</h2>

<p>Let’s start by saying that reading Django documentation for <code class="language-plaintext highlighter-rouge">filter()</code> method<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> is not really saying too much about default ordering:</p>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">filter(_*args_, _**kwargs_)</code><a href="https://docs.djangoproject.com/en/6.0/ref/models/querysets/#django.db.models.query.QuerySet.filter" title="Link to this definition">¶</a></p>

  <p>Returns a new <code class="language-plaintext highlighter-rouge">QuerySet</code> containing objects that match the given lookup parameters.</p>

  <p>The lookup parameters (<code class="language-plaintext highlighter-rouge">**kwargs</code>) should be in the format described in <a href="https://docs.djangoproject.com/en/6.0/ref/models/querysets/#id4">Field lookups</a> below. Multiple parameters are joined via <code class="language-plaintext highlighter-rouge">AND</code> in the underlying SQL statement.</p>

  <p>If you need to execute more complex queries (for example, queries with <code class="language-plaintext highlighter-rouge">OR</code> statements), you can use <a href="https://docs.djangoproject.com/en/6.0/ref/models/querysets/#django.db.models.Q" title="django.db.models.Q"><code class="language-plaintext highlighter-rouge">Q objects</code></a> (<code class="language-plaintext highlighter-rouge">*args</code>).</p>
</blockquote>

<p>and it’s not weird, because <code class="language-plaintext highlighter-rouge">QuerySet</code> depends on model configuration.</p>

<p>More information can be found in <code class="language-plaintext highlighter-rouge">django.db.models</code> documentation - specifically for <code class="language-plaintext highlighter-rouge">Options.ordering</code>.<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup> There’s a yellow callout with <strong><em>Warning</em></strong> that says explicitly:</p>

<blockquote>
  <p><strong>If a query doesn’t have an ordering specified, results are returned from the database in an unspecified order. A particular ordering is guaranteed only when ordering by a set of fields that uniquely identify each object in the results</strong>. For example, if a <code class="language-plaintext highlighter-rouge">name</code> field isn’t unique, ordering by it won’t guarantee objects with the same name always appear in the same order.</p>
</blockquote>

<p>This means that <code class="language-plaintext highlighter-rouge">filter()</code> returns objects in unspecified order, unless directly specified otherwise either in model definition or in the <code class="language-plaintext highlighter-rouge">filter</code> method itself.</p>

<p>But then, why it never failed when using <code class="language-plaintext highlighter-rouge">.first()</code>?</p>

<h2 id="how-does-first-pick-the-first-element-from-the-query">How does <code class="language-plaintext highlighter-rouge">first()</code> pick the first element from the query?</h2>

<p>Fortunately, the answer for this question is easier to find. It’s right in the documentation of <code class="language-plaintext highlighter-rouge">first()</code>:<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup></p>

<blockquote>
  <p>Returns the first object matched by the queryset, or <code class="language-plaintext highlighter-rouge">None</code> if there is no matching object. <strong>If the <code class="language-plaintext highlighter-rouge">QuerySet</code> has no ordering defined, then the queryset is automatically ordered by the primary key</strong>. This can affect aggregation results as described in <a href="https://docs.djangoproject.com/en/6.0/topics/db/aggregation/#aggregation-ordering-interaction">Interaction with order_by()</a>.</p>
</blockquote>

<p>So seemingly, <code class="language-plaintext highlighter-rouge">first()</code> adds an implicit <code class="language-plaintext highlighter-rouge">ORDER BY pk</code> to the <code class="language-plaintext highlighter-rouge">QuerySet</code>, which direct indexing (<code class="language-plaintext highlighter-rouge">[n]</code>) obviously does not.</p>

<p>That’s why the test was failing. When index is used to retrieve an object, it is returning the second row of an unordered result set. And <code class="language-plaintext highlighter-rouge">first()</code> was consistently picking the first created one.</p>

<h2 id="what-can-be-done-about-it">What can be done about it?</h2>

<p>There are multiple approaches to the problem.</p>

<h3 id="make-the-query-stricter">Make the query stricter</h3>

<p>This is what I did to fix the test. I added one more query parameter to ensure that I get only one instance of a <code class="language-plaintext highlighter-rouge">Service</code> from the query.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">second_service</span> <span class="o">=</span> <span class="n">Service</span><span class="p">.</span><span class="n">objects</span><span class="p">.</span><span class="nb">filter</span><span class="p">(</span>
    <span class="n">service_type</span><span class="o">=</span><span class="s">"L3Connection"</span><span class="p">,</span> <span class="n">related</span><span class="o">=</span><span class="n">some_other_service_instance</span>
<span class="p">).</span><span class="n">first</span><span class="p">()</span>
</code></pre></div></div>

<p>This approach is only as good as it’s feasible. It’s easy to apply, but sometimes adding new parameter is not really helping, and only makes the code unreadable. In my case, I could switch to using <code class="language-plaintext highlighter-rouge">get()</code> instead of <code class="language-plaintext highlighter-rouge">filter()</code> after applying the change.</p>

<h3 id="chain-order_by-with--filter">Chain <code class="language-plaintext highlighter-rouge">order_by</code> with  <code class="language-plaintext highlighter-rouge">filter</code></h3>

<p>Another approach is to specifically order the <code class="language-plaintext highlighter-rouge">QuerySet</code> retrieved by <code class="language-plaintext highlighter-rouge">filter()</code>:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">second_service</span> <span class="o">=</span> <span class="n">Service</span><span class="p">.</span><span class="n">objects</span><span class="p">.</span><span class="nb">filter</span><span class="p">(</span>
    <span class="n">service_type</span><span class="o">=</span><span class="s">"L3Connection"</span><span class="p">,</span>
<span class="p">).</span><span class="n">order_by</span><span class="p">(</span><span class="s">"created_at"</span><span class="p">,</span> <span class="s">"pk"</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span>
</code></pre></div></div>

<p>This approach adds some complexity to the query, but at least does not require any new state migrations to be performed. I use <code class="language-plaintext highlighter-rouge">created_at</code> timestamp here, but any other ordered field should do the job.</p>

<p>Note that if ordering is done only by <code class="language-plaintext highlighter-rouge">created_at</code>, and the same timestamp is used in more than one instance, non-deterministic ordering may still occur. Hence the usage of <code class="language-plaintext highlighter-rouge">pk</code>.</p>

<h3 id="specify-ordering-in-models-meta">Specify <code class="language-plaintext highlighter-rouge">ordering</code> in model’s <code class="language-plaintext highlighter-rouge">Meta</code></h3>

<p>To avoid taking care of sorting the <code class="language-plaintext highlighter-rouge">QuerySet</code> in place, adding <code class="language-plaintext highlighter-rouge">ordering</code> attribute to <code class="language-plaintext highlighter-rouge">Meta</code> of model class should do the job, too. It could be as simple as the following:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Service</span><span class="p">(</span><span class="n">models</span><span class="p">.</span><span class="n">Model</span><span class="p">):</span>
    <span class="k">class</span> <span class="nc">ServiceTypes</span><span class="p">(</span><span class="n">models</span><span class="p">.</span><span class="n">TextChoices</span><span class="p">):</span>
        <span class="n">L3_CONNECTION</span> <span class="o">=</span> <span class="s">"L3Connection"</span>
        <span class="n">ROUTING</span> <span class="o">=</span> <span class="s">"ROUTING"</span>

    <span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="p">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span> <span class="n">null</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
    <span class="n">service_type</span> <span class="o">=</span> <span class="n">models</span><span class="p">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span> <span class="n">choices</span><span class="o">=</span><span class="n">ServiceTypes</span><span class="p">)</span>
    <span class="n">created_at</span> <span class="o">=</span> <span class="n">models</span><span class="p">.</span><span class="n">DateTimeField</span><span class="p">(</span><span class="n">auto_now_add</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>

    <span class="c1"># Added ordering to the model
</span>    <span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
        <span class="n">ordering</span> <span class="o">=</span> <span class="p">[</span><span class="s">"created_at"</span><span class="p">,</span> <span class="s">"pk"</span><span class="p">]</span>

</code></pre></div></div>

<p>This approach requires additional migration being executed (even though no SQL changes are applied), but it might also be the cleanest long-term solution for the problem of undefined ordering of query sets.</p>

<h2 id="summary">Summary</h2>

<p>So as you can see, with great complexity of the database, more knowledge about Django ORM’s internals can be needed to solve obscure issues.</p>

<p>The problem I described was probably specific to the app I work on and its combination of Netbox, PostgreSQL and multi-endpoint workflows being tested.</p>

<p>Anyway, I wasted too much time on observing CI pipelines randomly failing on my branches because of someone else’s tests, so I decided to take a deeper look. And since the topic looked quite curious - I decided to describe it here.</p>

<p>Lesson learned: Make your <code class="language-plaintext highlighter-rouge">QuerySet</code>s deterministic, either via stricter filters, explicit ordering, or <code class="language-plaintext highlighter-rouge">Meta.ordering</code>.</p>

<blockquote>
  <p><em>That’s it for today. Happy hacking! 🐍</em></p>
</blockquote>

<hr />

<p><br /></p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p><a href="https://docs.djangoproject.com/en/6.0/ref/models/querysets/#filter">https://docs.djangoproject.com/en/6.0/ref/models/querysets/#filter</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p><a href="https://docs.djangoproject.com/en/6.0/ref/models/options/#django.db.models.Options.ordering">https://docs.djangoproject.com/en/6.0/ref/models/options/#django.db.models.Options.ordering</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p><a href="https://docs.djangoproject.com/en/6.0/ref/models/querysets/#django.db.models.query.QuerySet.first">https://docs.djangoproject.com/en/6.0/ref/models/querysets/#django.db.models.query.QuerySet.first</a> <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Kacper Borucki</name></author><summary type="html"><![CDATA[What if I told you that the following way of taking the second object from a Django QuerySet might be unreliable and can lead to non-deterministic failures under some circumstances? second_service = Service.objects.filter(service_type="L3Connection")[1]]]></summary></entry><entry><title type="html">How to parametrize exception testing in PyTest?</title><link href="/2026/01/15/how-to-parametrize-exception-testing-in-pytest.html" rel="alternate" type="text/html" title="How to parametrize exception testing in PyTest?" /><published>2026-01-15T00:00:00+00:00</published><updated>2026-01-15T00:00:00+00:00</updated><id>/2026/01/15/how-to-parametrize-exception-testing-in-pytest</id><content type="html" xml:base="/2026/01/15/how-to-parametrize-exception-testing-in-pytest.html"><![CDATA[<p>Sometimes it’s useful to provide different input data and test different exceptions being raised. Not every exception deserves its own unit test, though. In such cases, I tend to combine PyTest’s <code class="language-plaintext highlighter-rouge">parametrize</code> marker with Python’s <code class="language-plaintext highlighter-rouge">contextlib.nullcontext</code> builtin.</p>

<!--more-->

<p>Here’s how to use it:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">nullcontext</span> <span class="k">as</span> <span class="n">does_not_raise</span>
<span class="kn">import</span> <span class="nn">pytest</span>
<span class="kn">from</span> <span class="nn">pytest</span> <span class="kn">import</span> <span class="n">raises</span>


<span class="o">@</span><span class="n">pytest</span><span class="p">.</span><span class="n">mark</span><span class="p">.</span><span class="n">parametrize</span><span class="p">(</span>
    <span class="p">[</span><span class="s">"x"</span><span class="p">,</span> <span class="s">"y"</span><span class="p">,</span> <span class="s">"expectation"</span><span class="p">],</span>
    <span class="p">[</span>
        <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="n">does_not_raise</span><span class="p">()),</span>
        <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">does_not_raise</span><span class="p">()),</span>
        <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">raises</span><span class="p">(</span><span class="nb">ZeroDivisionError</span><span class="p">)),</span>
        <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s">"0"</span><span class="p">,</span> <span class="n">raises</span><span class="p">(</span><span class="nb">TypeError</span><span class="p">)),</span>
    <span class="p">],</span>
<span class="p">)</span>
<span class="k">def</span> <span class="nf">test_division</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">expectation</span><span class="p">):</span>
    <span class="k">with</span> <span class="n">expectation</span><span class="p">:</span>
        <span class="n">x</span> <span class="o">/</span> <span class="n">y</span>
</code></pre></div></div>

<p>The topic is actually described in the PyTest documentation<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> and it was even raised as a question on StackOverflow<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup> but I still feel like it’s pretty obscure knowledge.</p>

<p>I like to use this approach when testing exceptions is repetitive and I don’t need to cover it thoroughly. Or when I need to get as close to 100% code coverage as possible.</p>

<p>The <code class="language-plaintext highlighter-rouge">nullcontext</code> usage may be a pretty obscure piece of knowledge, but with a small rename on import, its purpose becomes much clearer for anyone randomly encountering such unit test.</p>

<blockquote>
  <p><em>That’s it for today. Happy hacking! 🐍</em></p>
</blockquote>

<hr />

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>PyTest documentation: <a href="https://docs.pytest.org/en/stable/example/parametrize.html#parametrizing-conditional-raising">https://docs.pytest.org/en/stable/example/parametrize.html#parametrizing-conditional-raising</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>Question from StackOverflow: <a href="https://stackoverflow.com/a/68012715/18577080">https://stackoverflow.com/a/68012715/18577080</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Kacper Borucki</name></author><summary type="html"><![CDATA[Sometimes it’s useful to provide different input data and test different exceptions being raised. Not every exception deserves its own unit test, though. In such cases, I tend to combine PyTest’s parametrize marker with Python’s contextlib.nullcontext builtin.]]></summary></entry><entry><title type="html">How to reuse Pydantic model_validator across multiple models without boilerplate code?</title><link href="/2025/12/22/how-to-reuse-pydantic-model_validator-across-multiple-models-without-boilerplate-code.html" rel="alternate" type="text/html" title="How to reuse Pydantic model_validator across multiple models without boilerplate code?" /><published>2025-12-22T00:00:00+00:00</published><updated>2025-12-22T00:00:00+00:00</updated><id>/2025/12/22/how-to-reuse-pydantic-model_validator-across-multiple-models-without-boilerplate-code</id><content type="html" xml:base="/2025/12/22/how-to-reuse-pydantic-model_validator-across-multiple-models-without-boilerplate-code.html"><![CDATA[<p>Recently I’ve been working with Pydantic models quite a lot. And I mean multiple models with multiple fields and validators. I noticed that in some cases, reusing validators in some smart way would reduce my codebase by even hundredths of lines of code. So I tried to come up with a solution for that problem…</p>

<p><img src="/assets/img/snippets/2025-12-22-how-to-reuse-pydantic-model_validator-across-multiple-models-without-boilerplate-code.png" alt="Code snippet" /></p>

<!--more-->

<h2 id="starting-point">Starting point</h2>

<p>Let’s say I need a model of a list with exactly three elements inside, enforced by model validator (yes, I know I don’t need a model validator to do that, but I needed a simplistic example).</p>

<p>Let’s call this model <code class="language-plaintext highlighter-rouge">SomeModel</code>. It would look something like this:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">SomeModel</span><span class="p">(</span><span class="n">pydantic</span><span class="p">.</span><span class="n">RootModel</span><span class="p">):</span>
    <span class="n">root</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span>

    <span class="o">@</span><span class="n">pydantic</span><span class="p">.</span><span class="n">model_validator</span><span class="p">(</span><span class="n">mode</span><span class="o">=</span><span class="s">"after"</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">length_should_be_3</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s">"SomeModel"</span><span class="p">:</span>
        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">root</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">self</span>
        <span class="k">raise</span> <span class="nb">ValueError</span>
</code></pre></div></div>

<p>Now let’s say that I need another model, that happens to perform exactly the same validation logic on similar field. Let’s call it <code class="language-plaintext highlighter-rouge">SomeOtherModel</code>:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">SomeOtherModel</span><span class="p">(</span><span class="n">pydantic</span><span class="p">.</span><span class="n">RootModel</span><span class="p">):</span>
    <span class="n">root</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span>

    <span class="o">@</span><span class="n">pydantic</span><span class="p">.</span><span class="n">model_validator</span><span class="p">(</span><span class="n">mode</span><span class="o">=</span><span class="s">"after"</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">length_should_be_3</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s">"SomeOtherModel"</span><span class="p">:</span>
        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">root</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">self</span>
        <span class="k">raise</span> <span class="nb">ValueError</span>
</code></pre></div></div>

<p>Copy-pasting the same logic in two models seem wasteful…</p>

<h2 id="extracting-validation-logic">Extracting validation logic</h2>

<p>…so maybe let’s try to extract validation logic to a separate function and reuse the logic by calling it within <code class="language-plaintext highlighter-rouge">model_validator</code>:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">length_should_be_3</span><span class="p">(</span><span class="n">model</span><span class="p">:</span> <span class="n">pydantic</span><span class="p">.</span><span class="n">RootModel</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">pydantic</span><span class="p">.</span><span class="n">RootModel</span><span class="p">:</span>
    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">model</span><span class="p">.</span><span class="n">root</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">model</span>
    <span class="k">raise</span> <span class="nb">ValueError</span>


<span class="k">class</span> <span class="nc">SomeModel</span><span class="p">(</span><span class="n">pydantic</span><span class="p">.</span><span class="n">RootModel</span><span class="p">):</span>
    <span class="n">root</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span>

    <span class="o">@</span><span class="n">pydantic</span><span class="p">.</span><span class="n">model_validator</span><span class="p">(</span><span class="n">mode</span><span class="o">=</span><span class="s">"after"</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">length_should_be_3</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s">"SomeModel"</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">length_should_be_3</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>

<span class="k">class</span> <span class="nc">SomeOtherModel</span><span class="p">(</span><span class="n">pydantic</span><span class="p">.</span><span class="n">RootModel</span><span class="p">):</span>
    <span class="n">root</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span>

    <span class="o">@</span><span class="n">pydantic</span><span class="p">.</span><span class="n">model_validator</span><span class="p">(</span><span class="n">mode</span><span class="o">=</span><span class="s">"after"</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">length_should_be_3</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s">"SomeOtherModel"</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">length_should_be_3</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
</code></pre></div></div>

<p>Looks a bit cleaner now, but notice that with each new model reusing the validation logic, I would have to copy-paste the same <code class="language-plaintext highlighter-rouge">length_should_be_3</code> method… Isn’t there a better way?</p>

<h2 id="extracting-model_validator">Extracting model_validator</h2>

<p>Good news: Pydantic supports even more minimal way of reusing validators. But it’s not mentioned in documentation - even though at some point it was, with example of <code class="language-plaintext highlighter-rouge">field_validator</code>.<sup id="fnref:reuse" role="doc-noteref"><a href="#fn:reuse" class="footnote" rel="footnote">1</a></sup></p>

<p>Using this approach, we can do something like below.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">length_should_be_3</span><span class="p">(</span><span class="n">model</span><span class="p">:</span> <span class="n">pydantic</span><span class="p">.</span><span class="n">RootModel</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">pydantic</span><span class="p">.</span><span class="n">RootModel</span><span class="p">:</span>
    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">model</span><span class="p">.</span><span class="n">root</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">model</span>
    <span class="k">raise</span> <span class="nb">ValueError</span>


<span class="k">class</span> <span class="nc">SomeModel</span><span class="p">(</span><span class="n">pydantic</span><span class="p">.</span><span class="n">RootModel</span><span class="p">):</span>
    <span class="n">root</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span>
    <span class="c1"># Validators
</span>    <span class="n">_validate_length</span> <span class="o">=</span> <span class="n">pydantic</span><span class="p">.</span><span class="n">model_validator</span><span class="p">(</span><span class="n">mode</span><span class="o">=</span><span class="s">"after"</span><span class="p">)(</span><span class="n">length_should_be_3</span><span class="p">)</span>


<span class="k">class</span> <span class="nc">SomeOtherModel</span><span class="p">(</span><span class="n">pydantic</span><span class="p">.</span><span class="n">RootModel</span><span class="p">):</span>
    <span class="n">root</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span>
    <span class="c1"># Validators
</span>    <span class="n">_validate_length</span> <span class="o">=</span> <span class="n">pydantic</span><span class="p">.</span><span class="n">model_validator</span><span class="p">(</span><span class="n">mode</span><span class="o">=</span><span class="s">"after"</span><span class="p">)(</span><span class="n">length_should_be_3</span><span class="p">)</span>
</code></pre></div></div>

<p>It’s even more minimal than previous iteration, isn’t it?</p>

<h2 id="but-does-this-even-work">But does this even work?</h2>

<p>Of course! And you can test it yourself, using the following single-file module with definitions and tests. Feel free to copy-paste it and run with PyTest (but remember to install <a href="https://docs.pydantic.dev/latest/install/"><code class="language-plaintext highlighter-rouge">Pydantic</code></a> and <a href="https://docs.pytest.org/en/stable/getting-started.html#install-pytest"><code class="language-plaintext highlighter-rouge">PyTest</code></a> first!):</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># test_pydantic_reused_validators.py
</span><span class="s">"""This files showcases possibility to reuse model validators across Pydantic models.

It is based on the following page of Pydantic documentation:
https://docs.pydantic.dev/2.0/usage/validators/#reuse-validators

It is not specified in newer versions of Pydantic documentation, although it works flawlessly,
reducing boilerplate code being added to models.
"""</span>

<span class="kn">import</span> <span class="nn">pydantic</span>
<span class="kn">import</span> <span class="nn">pytest</span>


<span class="k">def</span> <span class="nf">length_should_be_3</span><span class="p">(</span><span class="n">model</span><span class="p">:</span> <span class="n">pydantic</span><span class="p">.</span><span class="n">RootModel</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">pydantic</span><span class="p">.</span><span class="n">RootModel</span><span class="p">:</span>
    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">model</span><span class="p">.</span><span class="n">root</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">model</span>
    <span class="k">raise</span> <span class="nb">ValueError</span>


<span class="k">class</span> <span class="nc">SomeModel</span><span class="p">(</span><span class="n">pydantic</span><span class="p">.</span><span class="n">RootModel</span><span class="p">):</span>
    <span class="n">root</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span>

    <span class="c1"># Validators
</span>    <span class="n">_validate_length</span> <span class="o">=</span> <span class="n">pydantic</span><span class="p">.</span><span class="n">model_validator</span><span class="p">(</span><span class="n">mode</span><span class="o">=</span><span class="s">"after"</span><span class="p">)(</span><span class="n">length_should_be_3</span><span class="p">)</span>


<span class="k">class</span> <span class="nc">SomeOtherModel</span><span class="p">(</span><span class="n">pydantic</span><span class="p">.</span><span class="n">RootModel</span><span class="p">):</span>
    <span class="n">root</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span>

    <span class="c1"># Validators
</span>    <span class="n">_validate_length</span> <span class="o">=</span> <span class="n">pydantic</span><span class="p">.</span><span class="n">model_validator</span><span class="p">(</span><span class="n">mode</span><span class="o">=</span><span class="s">"after"</span><span class="p">)(</span><span class="n">length_should_be_3</span><span class="p">)</span>


<span class="o">@</span><span class="n">pytest</span><span class="p">.</span><span class="n">fixture</span><span class="p">(</span><span class="n">params</span><span class="o">=</span><span class="p">[</span><span class="n">SomeModel</span><span class="p">,</span> <span class="n">SomeOtherModel</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">model</span><span class="p">(</span><span class="n">request</span><span class="p">:</span> <span class="n">pytest</span><span class="p">.</span><span class="n">FixtureRequest</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">type</span><span class="p">[</span><span class="n">SomeModel</span><span class="p">]</span> <span class="o">|</span> <span class="nb">type</span><span class="p">[</span><span class="n">SomeOtherModel</span><span class="p">]:</span>
    <span class="k">return</span> <span class="n">request</span><span class="p">.</span><span class="n">param</span>


<span class="k">def</span> <span class="nf">test_both_models_can_be_built</span><span class="p">(</span>
    <span class="n">model</span><span class="p">:</span> <span class="nb">type</span><span class="p">[</span><span class="n">SomeModel</span><span class="p">]</span> <span class="o">|</span> <span class="nb">type</span><span class="p">[</span><span class="n">SomeOtherModel</span><span class="p">],</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
    <span class="c1"># Given
</span>    <span class="n">input_data</span> <span class="o">=</span> <span class="p">[</span><span class="s">"one"</span><span class="p">,</span> <span class="s">"two"</span><span class="p">,</span> <span class="s">"three"</span><span class="p">]</span>
    <span class="c1"># When
</span>    <span class="n">result</span> <span class="o">=</span> <span class="n">model</span><span class="p">.</span><span class="n">model_validate</span><span class="p">(</span><span class="n">input_data</span><span class="p">)</span>
    <span class="c1"># Then
</span>    <span class="k">assert</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="n">model</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">result</span><span class="p">.</span><span class="n">root</span> <span class="o">==</span> <span class="n">input_data</span>


<span class="k">def</span> <span class="nf">test_both_models_fail_on_length_other_than_3</span><span class="p">(</span>
    <span class="n">model</span><span class="p">:</span> <span class="nb">type</span><span class="p">[</span><span class="n">SomeModel</span><span class="p">]</span> <span class="o">|</span> <span class="nb">type</span><span class="p">[</span><span class="n">SomeOtherModel</span><span class="p">],</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
    <span class="c1"># Given
</span>    <span class="n">input_data</span> <span class="o">=</span> <span class="p">[</span><span class="s">"one"</span><span class="p">,</span> <span class="s">"two"</span><span class="p">,</span> <span class="s">"three"</span><span class="p">,</span> <span class="s">"four"</span><span class="p">]</span>
    <span class="c1"># Then
</span>    <span class="k">with</span> <span class="n">pytest</span><span class="p">.</span><span class="n">raises</span><span class="p">(</span><span class="nb">ValueError</span><span class="p">):</span>
        <span class="n">model</span><span class="p">.</span><span class="n">model_validate</span><span class="p">(</span><span class="n">input_data</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="but-doesnt-the-resulting-code-look-a-bit-unreadable">But doesn’t the resulting code look a bit unreadable?</h2>

<p>Yes, when taken out of context. But in a team that works with models on a daily basis, it shouldn’t be a big deal. Sometimes the code can’t be self-documenting, but hey - that’s what comments are for.</p>

<p>My main concern is that such usage of validators could get deprecated by Pydantic at some point, especially given the lack of documentation. But it seems like what I do here is basically calling a parametrized decorator directly, as a function. So the risk might be real, but looks like not not that high in the end.</p>

<blockquote>
  <p><em>That’s it for today. Happy hacking! 🐍</em></p>
</blockquote>

<hr />

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:reuse" role="doc-endnote">
      <p><a href="https://docs.pydantic.dev/2.0/usage/validators/#reuse-validators">https://docs.pydantic.dev/2.0/usage/validators/#reuse-validators</a> <a href="#fnref:reuse" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Kacper Borucki</name></author><summary type="html"><![CDATA[Recently I’ve been working with Pydantic models quite a lot. And I mean multiple models with multiple fields and validators. I noticed that in some cases, reusing validators in some smart way would reduce my codebase by even hundredths of lines of code. So I tried to come up with a solution for that problem…]]></summary></entry><entry><title type="html">How to check whether Python script has elevated privileges?</title><link href="/2025/10/16/how-to-check-whether-python-script-has-elevated-privileges.html" rel="alternate" type="text/html" title="How to check whether Python script has elevated privileges?" /><published>2025-10-16T00:00:00+00:00</published><updated>2025-10-16T00:00:00+00:00</updated><id>/2025/10/16/how-to-check-whether-python-script-has-elevated-privileges</id><content type="html" xml:base="/2025/10/16/how-to-check-whether-python-script-has-elevated-privileges.html"><![CDATA[<p>It may happen that a Python script needs <code class="language-plaintext highlighter-rouge">root</code> privileges on Linux / macOS or <code class="language-plaintext highlighter-rouge">admin</code> privileges on Windows to run properly. If it does not have them, there is no point in continuing. Let’s see how to quickly check whether the current runtime has those privileges.</p>

<!--more-->

<h2 id="linux--macos">Linux / macOS</h2>

<p>On Linux and macOS, this is straightforward. Elevated privileges are marked by user ID <code class="language-plaintext highlighter-rouge">0</code>, and Python built-in library <code class="language-plaintext highlighter-rouge">os</code> provides the function <code class="language-plaintext highlighter-rouge">getuid()</code><sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> that returns current user ID. Hence, it’s enough to just grab this value, compare it with <code class="language-plaintext highlighter-rouge">0</code> and return the result.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">os</span>

<span class="k">def</span> <span class="nf">is_root</span><span class="p">():</span>
    <span class="s">"""Return True if the current script is running on Linux/macOS with root privileges, otherwise False."""</span>
    <span class="k">if</span> <span class="n">os</span><span class="p">.</span><span class="n">name</span> <span class="o">==</span> <span class="s">"posix"</span><span class="p">:</span>
     <span class="c1"># On Linux, user ID 0 indicates the root user
</span>        <span class="k">return</span> <span class="n">os</span><span class="p">.</span><span class="n">getuid</span><span class="p">()</span> <span class="o">==</span> <span class="mi">0</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">return</span> <span class="bp">False</span>
</code></pre></div></div>

<h2 id="windows">Windows</h2>

<p>On Windows, the approach with user ID will not work because Windows manages users in a different way. But the check for admin privileges is still relatively simple. It requires using C libraries available on Windows, which can be accessed through Python’s built-in <code class="language-plaintext highlighter-rouge">ctypes</code> module.<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup></p>

<p>In this case, the most elegant approach I found was to call <code class="language-plaintext highlighter-rouge">windll.shell32.IsUserAnAdmin</code>.<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup> I’m not saying it’s the best solution, but it worked well when I needed it</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">ctypes</span>
<span class="kn">import</span> <span class="nn">os</span>

<span class="k">def</span> <span class="nf">is_admin</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
    <span class="s">"""Return True if the current script is running on Windows with admin privileges, otherwise False."""</span>
    <span class="k">if</span> <span class="n">os</span><span class="p">.</span><span class="n">name</span> <span class="o">==</span> <span class="s">"nt"</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">ctypes</span><span class="p">.</span><span class="n">windll</span><span class="p">.</span><span class="n">shell32</span><span class="p">.</span><span class="n">IsUserAnAdmin</span><span class="p">()</span> <span class="o">!=</span> <span class="mi">0</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">return</span> <span class="bp">False</span>
</code></pre></div></div>

<blockquote>
  <p><em>That’s it for today. Happy hacking! 🐍</em></p>
</blockquote>

<hr />

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p><a href="https://docs.python.org/3/library/os.html#os.getuid">https://docs.python.org/3/library/os.html#os.getuid</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p><a href="https://docs.python.org/3/library/ctypes.html">https://docs.python.org/3/library/ctypes.html</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p><a href="https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-isuseranadmin">https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-isuseranadmin</a> <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Kacper Borucki</name></author><summary type="html"><![CDATA[It may happen that a Python script needs root privileges on Linux / macOS or admin privileges on Windows to run properly. If it does not have them, there is no point in continuing. Let’s see how to quickly check whether the current runtime has those privileges.]]></summary></entry><entry><title type="html">»&amp;gt; print(“Hello World!”)</title><link href="/2025/10/03/hello-world.html" rel="alternate" type="text/html" title="»&amp;gt; print(“Hello World!”)" /><published>2025-10-03T00:00:00+00:00</published><updated>2025-10-03T00:00:00+00:00</updated><id>/2025/10/03/hello-world</id><content type="html" xml:base="/2025/10/03/hello-world.html"><![CDATA[<!--more-->

<p>Hi there!</p>

<p>My name is Kacper. I work as a Python developer and have experience with things like Django, Robot Framework, PyTest, GitLab, Docker and technologies surrounding it. Since I’ve worked with network automation, I can say I’m somewhat familiar with it, too.</p>

<p>I wanted to start this blog for quite a while. I’ve collected multiple notes with technical solutions, how-tos, guidelines, and general tips that I believe could be useful to others. So I decided to start sharing them here.</p>

<p>You may expect straightforward posts about topics like:</p>

<ul>
  <li>Python (core functionalities, pro tips, optimisation) 🐍</li>
  <li>Python libraries (Pydantic, Jinja2, Django, Flask) 📚</li>
  <li>TDD and unit tests (unittest, PyTest, how to write testable code) 🧪</li>
  <li>Quality assurance (Robot Framework, BDD, test strategies) 🤖</li>
  <li>DevOps and related tools (Docker, GitLab, GitHub) 🐳</li>
  <li>Network automation (protocols, how-to articles, examples) 🕸️</li>
  <li>Tools I use daily (Visual Studio Code, Obsidian, Jekyll) 🧑‍💻</li>
  <li>Solutions to random technical problems with Linux / macOS / Windows 🧑‍🔧</li>
  <li>Software architecture (architectural styles, design patterns, clean code, technical writing) 🏛️</li>
  <li>Technical books (short reviews) 📚</li>
</ul>

<p>I don’t want to bloat my posts with chatter (unless I clearly warn you in advance). I mean to provide simple solutions for problems I came across working with code. More topics to come as I learn.</p>

<p>The first few posts will be mostly Python-related. These will include various tips I wrote about using #DailyPythonista hashtag on social media, recent notes about code I worked with, and examples of code.</p>

<p>When all of that is here, I will try to write a series of posts about Robot Framework - from beginner to pro. It seems like this topic is not much covered on the web, especially regarding new versions of the tool.</p>

<p>Feel free to reach out on social media if you’d like to ask something specific, share feedback, discuss a topic I might be familiar with, or even talk about a job offer. I’m open to discussion.</p>

<h2 id="by-the-way-various-ways-to-print-hello-world-in-python">By the way: various ways to print <code class="language-plaintext highlighter-rouge">"Hello world"</code> in Python</h2>

<p>To leave you with something “useful”, let’s see multiple ways to print <code class="language-plaintext highlighter-rouge">"Hello world"</code> in Python. The list of examples is not exhaustive and could easily be extended. I just wanted to have some wordplay with the title of a popular movie.</p>

<h3 id="the-good">The good</h3>

<p>Good because it’s simple.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="k">print</span><span class="p">(</span><span class="s">"Hello world"</span><span class="p">)</span>
<span class="n">Hello</span> <span class="n">world</span>
</code></pre></div></div>

<p>Still good anyway, even though variable name is vague:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="n">x</span> <span class="o">=</span> <span class="s">"Hello world"</span>
<span class="o">&gt;&gt;&gt;</span> <span class="k">print</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
<span class="n">Hello</span> <span class="n">world</span>
</code></pre></div></div>

<p>Good because it has neat padding in the output:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="n">x</span> <span class="o">=</span> <span class="s">"Hello world"</span>
<span class="o">&gt;&gt;&gt;</span> <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"|</span><span class="si">{</span><span class="n">x</span><span class="si">:</span><span class="o">^</span><span class="mi">20</span><span class="si">}</span><span class="s">|"</span><span class="p">)</span>
<span class="o">|</span>    <span class="n">Hello</span> <span class="n">world</span>     <span class="o">|</span>
</code></pre></div></div>

<h3 id="the-bad">The bad</h3>

<p>Bad because it’s over-engineered:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="k">def</span> <span class="nf">hello_world_factory</span><span class="p">():</span> <span class="k">return</span> <span class="s">"Hello world"</span>
<span class="p">...</span>
<span class="o">&gt;&gt;&gt;</span> <span class="k">print</span><span class="p">.</span><span class="n">__call__</span><span class="p">(</span><span class="n">hello_world_factory</span><span class="p">())</span>
<span class="n">Hello</span> <span class="n">world</span>
</code></pre></div></div>

<h3 id="the-ugly">The ugly</h3>

<p>Ugly because of left padding:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="n">x</span> <span class="o">=</span> <span class="s">"Hello world"</span>
<span class="o">&gt;&gt;&gt;</span> <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"|</span><span class="si">{</span><span class="n">x</span><span class="si">:</span><span class="o">&lt;</span><span class="mi">20</span><span class="si">}</span><span class="s">|"</span><span class="p">)</span>
<span class="o">|</span><span class="n">Hello</span> <span class="n">world</span>         <span class="o">|</span>
</code></pre></div></div>

<p>Ugly because of right padding:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="n">x</span> <span class="o">=</span> <span class="s">"Hello world"</span>
<span class="o">&gt;&gt;&gt;</span> <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"|</span><span class="si">{</span><span class="n">x</span><span class="si">:</span><span class="o">&gt;</span><span class="mi">20</span><span class="si">}</span><span class="s">|"</span><span class="p">)</span>
<span class="o">|</span>         <span class="n">Hello</span> <span class="n">world</span><span class="o">|</span>
</code></pre></div></div>

<blockquote>
  <p><em>That’s it for today. Happy hacking!</em></p>
</blockquote>]]></content><author><name>Kacper Borucki</name></author><summary type="html"><![CDATA[]]></summary></entry></feed>