<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dmitrii Doroshev</title>
    <description>The latest articles on DEV Community by Dmitrii Doroshev (@pomidoroshev).</description>
    <link>https://dev.to/pomidoroshev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F700723%2F7d8bc25b-3f5e-4411-8574-740ac4adb850.jpeg</url>
      <title>DEV Community: Dmitrii Doroshev</title>
      <link>https://dev.to/pomidoroshev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pomidoroshev"/>
    <language>en</language>
    <item>
      <title>This Week in Changelogs: curl</title>
      <dc:creator>Dmitrii Doroshev</dc:creator>
      <pubDate>Tue, 24 Mar 2026 00:23:17 +0000</pubDate>
      <link>https://dev.to/pomidoroshev/this-week-in-changelogs-curl-1d08</link>
      <guid>https://dev.to/pomidoroshev/this-week-in-changelogs-curl-1d08</guid>
      <description>&lt;p&gt;Hey everyone, long time no see!&lt;/p&gt;

&lt;p&gt;I started TWiC in 2023, and to be honest, mining diffs manually was exhausting; that's why the project faded away pretty quickly. Today, with a little bit of LLM help and automation, it has become much easier to find hidden gems in modern OSS and bring them to the audience. There's another problem though: sometimes there are too many gems, and I definitely don't want to restart a series of boring longreads.&lt;/p&gt;

&lt;p&gt;So today, we're gonna cover recent changes in only one project, (arguably) the most popular library and command-line tool in the world - &lt;a href="https://github.com/curl/curl" rel="noopener noreferrer"&gt;curl&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zip bomb protection via delivered-bytes tracking
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/curl/curl/commit/77ed315096" rel="noopener noreferrer"&gt;Commit&lt;/a&gt;, &lt;a href="https://github.com/curl/curl/pull/20787" rel="noopener noreferrer"&gt;PR&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://en.wikipedia.org/wiki/Zip_bomb" rel="noopener noreferrer"&gt;zip bomb&lt;/a&gt; is a relatively small compressed piece of data that is automatically decompressed on the client side into a giant blob, causing a DoS. One of the ways to protect against it is to set a limit on the incoming data size. Curl has had an option for it, &lt;a href="https://curl.se/libcurl/c/CURLOPT_MAXFILESIZE.html" rel="noopener noreferrer"&gt;CURLOPT_MAXFILESIZE&lt;/a&gt;, since 2003.&lt;/p&gt;

&lt;p&gt;There was one small problem: before 8.20.0 it only affected the number of &lt;em&gt;downloaded&lt;/em&gt; (compressed) bytes. The good news is that now they calculate &lt;em&gt;delivered&lt;/em&gt; (decompressed) bytes separately, extending &lt;code&gt;CURLOPT_MAXFILESIZE&lt;/code&gt; to act as zip bomb protection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* check that the 'delta' amount of bytes are okay to deliver to the
   application, or return error if not. */&lt;/span&gt;
&lt;span class="n"&gt;CURLcode&lt;/span&gt; &lt;span class="nf"&gt;Curl_pgrs_deliver_check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Curl_easy&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_filesize&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
     &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;curl_off_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_filesize&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deliver&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;failf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Would have exceeded max file size"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;CURLE_FILESIZE_EXCEEDED&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;CURLE_OK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Btw, check out the &lt;a href="https://github.com/curl/curl/pull/20787" rel="noopener noreferrer"&gt;pull request&lt;/a&gt; itself: &lt;em&gt;five&lt;/em&gt; AI bots and zero people besides the author (Daniel himself) were involved in the code review! And it actually helps a lot, because, well, check out the next one:&lt;/p&gt;

&lt;h2&gt;
  
  
  API bitmask copy-paste bug
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/curl/curl/commit/57a94fec47" rel="noopener noreferrer"&gt;Commit&lt;/a&gt;, &lt;a href="https://github.com/curl/curl/pull/20968" rel="noopener noreferrer"&gt;PR&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faw0hpu03gl2k8tbc1ol3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faw0hpu03gl2k8tbc1ol3.png" width="800" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CURLMNWC_CLEAR_DNS&lt;/code&gt; and &lt;code&gt;CURLMNWC_CLEAR_CONNS&lt;/code&gt; were both defined as &lt;code&gt;(1L &amp;lt;&amp;lt; 0)&lt;/code&gt; (or just &lt;code&gt;1&lt;/code&gt;), so you could never clear just DNS or just connections - always both.&lt;/p&gt;

&lt;p&gt;The bug was introduced mid-2025 (&lt;a href="https://github.com/curl/curl/pull/17613" rel="noopener noreferrer"&gt;PR&lt;/a&gt;, &lt;a href="https://github.com/curl/curl/pull/17613/files#diff-5a2041c31e9e495236b84364952b822e028216bd2c9c9da4d1281118885d6418R406-R414" rel="noopener noreferrer"&gt;diff&lt;/a&gt;), and despite having 3 people involved as code reviewers and 2 approvals, it made it into 8.16.0 to 8.19.0:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmfnzx8w0sbgepty6sp6b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmfnzx8w0sbgepty6sp6b.png" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Luckily, Codex Security found it, and the author of the original changeset came up with an elegant solution, introducing &lt;code&gt;CURLMNWC_CLEAR_ALL&lt;/code&gt; as the new &lt;code&gt;(1L &amp;lt;&amp;lt; 0)&lt;/code&gt; (since that's what every existing caller was unknowingly doing), and reassigning DNS and CONNS to bits 1 and 2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;CURLMNWC_CLEAR_ALL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="cm"&gt;/* In the beginning, all values available to set were 1 by mistake. We
     converted this to mean "all", thus setting all the bits
     automatically */&lt;/span&gt;
  &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CURLMNWC_CLEAR_DNS&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;CURLMNWC_CLEAR_CONNS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So yeah, if you can afford to involve complex static analyzers or modern AI-reviewers, you'd better do that, because even seasoned lead devs can miss such small issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Missing &lt;code&gt;return&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/curl/curl/commit/53a3b2114a9b0652bb464437d38f5c8abffc708d" rel="noopener noreferrer"&gt;Commit&lt;/a&gt;, &lt;a href="https://github.com/curl/curl/pull/20883" rel="noopener noreferrer"&gt;PR&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Long story short, there was a helper called &lt;code&gt;return_quote_error()&lt;/code&gt;. Thanks to its misleading name, the actual return statement was forgotten before the call, causing &lt;a href="https://hackerone.com/reports/3597359" rel="noopener noreferrer"&gt;segfaults&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is a classic case of a fix so small you're 100% sure you're right, you skip the review and just &lt;a href="https://github.com/curl/curl/pull/19030" rel="noopener noreferrer"&gt;merge&lt;/a&gt; broken code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F53ynhrr4l3a84n7kmmqt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F53ynhrr4l3a84n7kmmqt.png" width="800" height="719"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I personally like that apart from fixing this problem, they removed the &lt;code&gt;return_&lt;/code&gt; prefix from the function name to avoid future confusion. Of course, such problems will also be caught by AI checkers, but here's another story:&lt;/p&gt;

&lt;h2&gt;
  
  
  PowerPC64 endianness AI-slop
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/curl/curl/commit/21fc17b265" rel="noopener noreferrer"&gt;Commit&lt;/a&gt; - proudly &lt;code&gt;Co Authored By Claude Opus 4.6 (1M context)&lt;/code&gt; (not kher sobachiy!)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/curl/curl/commit/e9eddedf38" rel="noopener noreferrer"&gt;Revert&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A &lt;a href="https://github.com/curl/curl/pull/20985" rel="noopener noreferrer"&gt;PR&lt;/a&gt; from a user named &lt;a href="https://github.com/Scottcjn" rel="noopener noreferrer"&gt;AutoJanitor&lt;/a&gt; with a robot avatar and shitloads of contributions since the &lt;a href="https://en.wikipedia.org/wiki/OpenClaw" rel="noopener noreferrer"&gt;end of January 2026&lt;/a&gt; added &lt;code&gt;__powerpc64__&lt;/code&gt; to the list of architectures that can use unaligned memory access for MD5/MD4 fast paths with the following description:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Currently the fast path only covers &lt;code&gt;__i386__&lt;/code&gt;, &lt;code&gt;__x86_64__&lt;/code&gt;, and &lt;code&gt;__vax__&lt;/code&gt;. PowerPC64 (both LE and BE) &lt;em&gt;(sic! - &lt;a class="mentioned-user" href="https://dev.to/dima"&gt;@dima&lt;/a&gt;)&lt;/em&gt; supports efficient unaligned memory access and should use the same optimization.&lt;/p&gt;

&lt;p&gt;&amp;lt;...&amp;gt;&lt;/p&gt;

&lt;p&gt;Verified on IBM POWER8 S824 (ppc64le) — unaligned 32-bit loads work correctly and produce single &lt;code&gt;lwz&lt;/code&gt; instructions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To understand what it means, let's check out the code itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/*
 * SET reads 4 input bytes in little-endian byte order and stores them
 * in a properly aligned word in host byte order.
 *
 * The check for little-endian architectures that tolerate unaligned memory
 * accesses is an optimization. Nothing will break if it does not work.
 */&lt;/span&gt;
&lt;span class="cp"&gt;#if defined(__i386__) || defined(__x86_64__) || \
    defined(__vax__) || defined(__powerpc64__)
&lt;/span&gt;                  &lt;span class="cm"&gt;/* NB ^^^^^^^^^^^^^^^^^^^^^^ */&lt;/span&gt;
&lt;span class="cp"&gt;#define MD4_SET(n) (*(const uint32_t *)(const void *)&amp;amp;ptr[(n) * 4])
&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So this is an optimization for &lt;a href="https://en.wikipedia.org/wiki/Endianness" rel="noopener noreferrer"&gt;little-endian&lt;/a&gt; architectures, and, as pointed out later, causes undefined behavior.&lt;/p&gt;

&lt;p&gt;Anyway, the contribution looked rock-solid, got merged, and a few days later, a &lt;a href="https://github.com/thesamesam" rel="noopener noreferrer"&gt;real person&lt;/a&gt; (the guy has a website with &lt;code&gt;/~sam/&lt;/code&gt; - he knows his shit) came into the original PR, a discussion happened, and the commit was reverted.&lt;/p&gt;

&lt;p&gt;The thing is that while PowerPC can run in either endianness, the preprocessor check &lt;code&gt;__powerpc64__&lt;/code&gt; doesn't tell you which one. The optimization above is exclusively for little-endian cases (casting byte arrays directly to &lt;code&gt;uint32_t *&lt;/code&gt;, causing unaligned memory access), so on big-endian PowerPC64 it produced wrong results or just failed.&lt;/p&gt;

&lt;p&gt;Unfortunately, AI bots have once again ruined things slightly for Daniel Stenberg. Apparently, that's the fate of popular projects these days ¯\_(ツ)_/¯&lt;/p&gt;




&lt;p&gt;Thank you, folks! I hope you enjoyed this "episode" of This Week in Changelogs! Next time, fewer AI slop problems and more things to actually learn from.&lt;/p&gt;

</description>
      <category>twic</category>
      <category>todayilearned</category>
      <category>ai</category>
      <category>curl</category>
    </item>
    <item>
      <title>The Pitfalls of Reading User Input in C: a Story About scanf and Stdin</title>
      <dc:creator>Dmitrii Doroshev</dc:creator>
      <pubDate>Thu, 11 Dec 2025 21:40:45 +0000</pubDate>
      <link>https://dev.to/pomidoroshev/the-pitfalls-of-reading-user-input-in-c-a-story-about-scanf-and-stdin-2mj8</link>
      <guid>https://dev.to/pomidoroshev/the-pitfalls-of-reading-user-input-in-c-a-story-about-scanf-and-stdin-2mj8</guid>
      <description>&lt;p&gt;I recently had to write a piece of C code that takes some input from &lt;code&gt;stdin&lt;/code&gt;, ignores the newline, discards whatever exceeds the buffer, and does this repeatedly in a loop.&lt;/p&gt;

&lt;p&gt;Knowing something about &lt;code&gt;scanf&lt;/code&gt; syntax (&lt;a href="https://man7.org/linux/man-pages/man3/fscanf.3p.html" rel="noopener noreferrer"&gt;man&lt;/a&gt;) I came up with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;take_input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;gt; "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;scanf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%19[^&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"input=`%s`&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;take_input&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Note: The original code used an infinite loop, but a simple &lt;code&gt;for&lt;/code&gt; is enough to demonstrate the behavior.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When I ran it, the result was surprising:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gcc -o main main.c
$ ./main
&amp;gt; hello world↵
input=`hello world`
&amp;gt; input=`hello world`
&amp;gt; input=`hello world`
&amp;gt; input=`hello world`
&amp;gt; input=`hello world`
$ █
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It consumed the string once and printed the same value 5 times. Why?&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Stack (Ghost Data)
&lt;/h2&gt;

&lt;p&gt;Stack behavior explains the first confusing part. Although &lt;code&gt;char buf[20]&lt;/code&gt; is a locally scoped variable, in this particular case, each invocation of &lt;code&gt;take_input()&lt;/code&gt; ends up using the exact same stack memory address.&lt;/p&gt;

&lt;p&gt;Printint the address makes this undeniable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"address=%p, input=`%s`&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ./main
&amp;gt; hello world
address=0x7ffcb96c49d0, input=`hello world`
&amp;gt; address=0x7ffcb96c49d0, input=`hello world`
&amp;gt; address=0x7ffcb96c49d0, input=`hello world`
&amp;lt;...&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the buffer wasn't initialized, and because subsequent &lt;code&gt;scanf&lt;/code&gt; calls failed to overwrite it, the old content (the "ghost data") remained unchanged. Initializing the buffer with zeroes fixes the specific issue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the buffer resets each time, but &lt;code&gt;scanf&lt;/code&gt; still behaves oddly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ./main
&amp;gt; hello world
address=0x7ffea5c2d840, input=`hello world`
&amp;gt; address=0x7ffea5c2d840, input=``
&amp;gt; address=0x7ffea5c2d840, input=``
&amp;lt;...&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Scanf, Scansets and Stdin
&lt;/h2&gt;

&lt;p&gt;The real enemy here is the scanset &lt;code&gt;%19[^\n]&lt;/code&gt;. It reads up to 19 characters (&lt;code&gt;len(buf) - 1&lt;/code&gt;) that are &lt;em&gt;not&lt;/em&gt; a newline, but it &lt;strong&gt;does not consume the newline itself&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's open &lt;code&gt;gdb&lt;/code&gt; and inspect &lt;code&gt;stdin&lt;/code&gt; to see the wreckage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gcc -g3 -O0 main.c -o main
$ gdb main
(gdb) break 8
Breakpoint 1 at 0x11d3: file main.c, line 8.
(gdb) run
&amp;lt;...&amp;gt;
&amp;gt; hello world↵
&amp;lt;...&amp;gt;
(gdb) p *stdin
$1 = {
    _flags = -72539512,
    _IO_read_ptr = 0x55555555972b "\n",
    _IO_read_end = 0x55555555972c "",
    _IO_read_base = 0x555555559720 "hello world\n",
    &amp;lt;...&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;stdin-&amp;gt;_IO_read_ptr&lt;/code&gt;, the current position of input, still points to the newline. It was not consumed by &lt;code&gt;scanf&lt;/code&gt;; it is still sitting in &lt;code&gt;stdin&lt;/code&gt;, waiting to be digested.&lt;/p&gt;

&lt;p&gt;When the loop runs again, &lt;code&gt;scanf("%19[^\n]", buf)&lt;/code&gt; sees the newline immediately, matches zero characters, and aborts. The buffer stays empty (or zeroed), and the loop spins.&lt;/p&gt;

&lt;p&gt;Unlike Python's expression &lt;code&gt;input()[:19]&lt;/code&gt;, which consumes the newline and truncates the string cleanly, C's &lt;code&gt;scanf&lt;/code&gt; cannot express that behavior using a single scanset. I found three workable solutions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution A: The "Double Tap" (Extra &lt;code&gt;scanf&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;We can use extra &lt;code&gt;scanf&lt;/code&gt; calls to force the consumption of the remainder of the line and the newline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;take_input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;gt; "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;scanf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%19[^&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="cm"&gt;/* discard the rest of line, if it's &amp;gt;19 chars */&lt;/span&gt;
    &lt;span class="n"&gt;scanf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%*[^&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;]"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="cm"&gt;/* discard the newline */&lt;/span&gt;
    &lt;span class="n"&gt;scanf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%*c"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"input=`%s`&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks messy, but it works reliably for whitespace, short inputs, and long truncated inputs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ./main
&amp;gt; hey
input=`hey`
&amp;gt; hello world
input=`hello world`
&amp;gt; a b c d e f g h i j k l m o p q r s t u v w x y z
input=`a b c d e f g h i j`
&amp;gt; ␣␣␣␣␣    /* 5 whitespaces */
input=`     `
&amp;gt; abcdefghijklmnpoqrstuvwxyz
input=`abcdefghijklmnpoqrs`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Solution B: &lt;code&gt;fgets&lt;/code&gt; (The Standard Way)
&lt;/h3&gt;

&lt;p&gt;A more predictable and explicit approach is &lt;code&gt;fgets&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;string.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;take_input_2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;gt; "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;newline_ptr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strchr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newline_ptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="cm"&gt;/* replace \n with \0 to trim it */&lt;/span&gt;
            &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;newline_ptr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sc"&gt;'\0'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="cm"&gt;/* no newline found, meaning the input
               was truncated; we must consume
               the rest of the line from stdin */&lt;/span&gt;
            &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getchar&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sc"&gt;'\n'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;EOF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"input=`%s`&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Solution C: &lt;code&gt;getline&lt;/code&gt; (The Heap Way)
&lt;/h3&gt;

&lt;p&gt;There is a handy &lt;code&gt;getline&lt;/code&gt; function (&lt;a href="https://man7.org/linux/man-pages/man3/getline.3.html" rel="noopener noreferrer"&gt;man&lt;/a&gt;), which is not in ISO C but exists in POSIX. The difference is that &lt;code&gt;getline&lt;/code&gt; scans the whole line and allocates the buffer on the heap. You don't have to care about buffer boundaries anymore, but you do need to manually &lt;code&gt;free&lt;/code&gt; the memory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;string.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdlib.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;take_input_3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/* initialize the pointer with NULL */&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="cm"&gt;/* getline will return the updated capacity of `line`
       - in practice it grows dynamically */&lt;/span&gt;
    &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;cap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;ssize_t&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;gt; "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="cm"&gt;/* n contains the actual length of the input line,
       or -1 on failure or EOF */&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cm"&gt;/* remove newline */&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sc"&gt;'\0'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"input=`%s`&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="cm"&gt;/* NB: always free the heap memory */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Outcomes
&lt;/h2&gt;

&lt;p&gt;In the end, the exercise became a small reminder of how surprisingly tricky input handling in C can be. &lt;code&gt;scanf&lt;/code&gt; looks convenient until you hit edge cases around whitespace and newlines. At that point, "convenience" becomes a liability.&lt;/p&gt;

&lt;p&gt;The takeaway is simple: know your tools. If you need predictable, portable, line-oriented input with truncation handling, &lt;code&gt;fgets&lt;/code&gt; is almost always the better choice. If you have the luxury of POSIX and heap allocation, &lt;code&gt;getline&lt;/code&gt; offers freedom. But &lt;code&gt;scanf&lt;/code&gt;? Save that for when you know exactly what the input looks like.&lt;/p&gt;




&lt;h2&gt;
  
  
  Update 2025-12-14
&lt;/h2&gt;

&lt;p&gt;As Paul J. Lucas &lt;a href="https://dev.to/pauljlucas/comment/331ff"&gt;mentioned&lt;/a&gt;, there's no good solution for handling a large or endless line:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The problem with &lt;code&gt;getline&lt;/code&gt; is that malicious input could send a &lt;em&gt;very&lt;/em&gt; long line and crash your program due to running out of memory.&lt;/p&gt;

&lt;p&gt;&amp;lt;...&amp;gt;&lt;/p&gt;

&lt;p&gt;The only safe way is to use &lt;code&gt;fgets&lt;/code&gt; &amp;lt;...&amp;gt; — but this assumes malicious input even &lt;em&gt;has&lt;/em&gt; a newline, so you might not crash, but could be looping forever. You &lt;em&gt;could&lt;/em&gt; also check the number of characters read and finally exit in defeat if you've exceeded some arbitrarily large number.&lt;br&gt;
There's no perfect solution when you can't trust your input (which you &lt;strong&gt;&lt;em&gt;never&lt;/em&gt;&lt;/strong&gt; should).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So all the examples above will fail if you run something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ yes | tr -d '\n' | ./main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;take_input&lt;/code&gt; (&lt;code&gt;scanf&lt;/code&gt;) and &lt;code&gt;take_input_2&lt;/code&gt; (&lt;code&gt;fgets&lt;/code&gt; + &lt;code&gt;while ((c = getchar()) != '\n' &amp;lt;...&amp;gt;&lt;/code&gt;) will loop forever, and &lt;code&gt;take_input_3&lt;/code&gt; will eventually cause an OOM condition.&lt;/p&gt;

&lt;p&gt;However, we can slightly change the &lt;code&gt;while&lt;/code&gt; loop in the &lt;code&gt;fgets&lt;/code&gt; implementation, as recommended, crashing the program if the length of the skipped "tail" is longer than we can reasonably expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#define MAX_TAIL_LENGTH (10 * 1024)
&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;take_input_2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;gt; "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;newline_ptr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strchr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newline_ptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;newline_ptr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sc"&gt;'\0'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getchar&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sc"&gt;'\n'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;EOF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;MAX_TAIL_LENGTH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="s"&gt;"The input string is unexpectedly long.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="n"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"input=`%s`&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ yes | tr -d '\n' | ./main
The input string is unexpectedly long.
Aborted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an explicit, defensive failure mode rather than a silent hang.&lt;/p&gt;

</description>
      <category>c</category>
      <category>posix</category>
      <category>scanf</category>
    </item>
    <item>
      <title>Building a Toy Database: Concurrency is Hard</title>
      <dc:creator>Dmitrii Doroshev</dc:creator>
      <pubDate>Sat, 16 Aug 2025 21:53:25 +0000</pubDate>
      <link>https://dev.to/pomidoroshev/building-a-toy-database-concurrency-is-hard-32ec</link>
      <guid>https://dev.to/pomidoroshev/building-a-toy-database-concurrency-is-hard-32ec</guid>
      <description>&lt;p&gt;Hey folks, it's been a while. Here's some news on &lt;a href="https://github.com/ddoroshev/bazoola" rel="noopener noreferrer"&gt;Bazoola&lt;/a&gt;, my precious toy database, which already reached version 0.0.7!&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo App
&lt;/h2&gt;

&lt;p&gt;Meet the "Task manager" - a flask-based app demonstrating current capabilities of Bazoola:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu7ggprlt1fgb5c3krl85.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu7ggprlt1fgb5c3krl85.png" alt="Task manager demo" width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In order to run the task manager:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ddoroshev/bazoola.git
&lt;span class="nb"&gt;cd &lt;/span&gt;bazoola
pip &lt;span class="nb"&gt;install &lt;/span&gt;bazoola
python demo/app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open &lt;a href="http://127.0.0.1:5000" rel="noopener noreferrer"&gt;http://127.0.0.1:5000&lt;/a&gt; in your browser, and Bob's your uncle. The schemas and tables are all pre-created and filled with demo data, so you can browse and edit it, checking the explanation at the bottom of the page. For example, you can try to search design-related projects and tasks by "des" query:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4v3scnm0ibwooaxinzjn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4v3scnm0ibwooaxinzjn.png" alt="Search page" width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you scroll down a bit, there's an explanation of how it actually works:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyz9fiuoioch4bhyhowhv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyz9fiuoioch4bhyhowhv.png" alt="Explanation" width="800" height="497"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm still working on the &lt;code&gt;TEXT&lt;/code&gt; field implementation, so for now all descriptions and comments are basically &lt;code&gt;CHAR(200)&lt;/code&gt;, but this will change soon.&lt;/p&gt;

&lt;p&gt;Also, cherry on top - this demo app has been almost entirely generated by ✨&lt;a href="https://www.anthropic.com/claude-code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt;! That was a huge boost for me, because it helped focus on the essentials, delegating the boring stuff to AI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concurrency
&lt;/h2&gt;

&lt;p&gt;Finally, this thing can be run in concurrent environment: in parallel threads or even in parallel processes. There are no parallel operations though, but at least the data won't corrupt if you run two apps using the same &lt;code&gt;data&lt;/code&gt; directory simultaneously.&lt;/p&gt;

&lt;p&gt;The key is a global lock. In a nutshell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ...
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="c1"&gt;# ...
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tbl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;joins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# ...
&lt;/span&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;find_all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="c1"&gt;# ...
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;joins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# ...
&lt;/span&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;find_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;# ...
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delete_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;delete_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="c1"&gt;# ...
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;update_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Actually, there are two global locks under the hood:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;code&gt;.lock&lt;/code&gt; file, essentially a global mutex shared between the processes via &lt;a href="https://docs.python.org/3/library/fcntl.html#fcntl.flock" rel="noopener noreferrer"&gt;flock&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;threading.RLock&lt;/code&gt; object, shared between threads of a single process. However, &lt;code&gt;flock&lt;/code&gt; is not enough, because it's basically useless within the same process.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# storage.py
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ...
&lt;/span&gt;    &lt;span class="nd"&gt;@contextmanager&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;fcntl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fileno&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;fcntl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LOCK_EX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt;
        &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;fcntl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fileno&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;fcntl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LOCK_UN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ...
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TableStorage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cls_tables&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base_dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# ...
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_threadlock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RLock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_lockfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.lock&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;base_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# ...
&lt;/span&gt;
    &lt;span class="nd"&gt;@contextmanager&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_threadlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_lockfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's not about atomicity, it doesn't solve data consistency in general, i.e. if you unplug your computer in the middle of a write operation, you'll most likely lose some ¯\_(ツ)_/¯&lt;/p&gt;

&lt;h2&gt;
  
  
  Elimination of &lt;code&gt;find_by&lt;/code&gt; and &lt;code&gt;find_by_substr&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Initially, I had two specific functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;find_by(table_name, field_name, value)&lt;/code&gt; - an equivalent of &lt;code&gt;WHERE field_name == value&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;find_by_substr(table_name, field_name, substr)&lt;/code&gt; - &lt;code&gt;WHERE field_name LIKE substr&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, there's already &lt;code&gt;find_by_cond()&lt;/code&gt;, allowing to search by arbitrary conditions, defined in &lt;a href="https://github.com/ddoroshev/bazoola/blob/7f3aa725d61dc9113277258a21e0a17faa3b49e5/bazoola/cond.py" rel="noopener noreferrer"&gt;cond.py&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So I just added &lt;code&gt;EQ&lt;/code&gt;, &lt;code&gt;SUBSTR&lt;/code&gt; and &lt;code&gt;ISUBSTR&lt;/code&gt; and removed these two helpers in favor of &lt;code&gt;find_by_cond(table_name, EQ(field=value))&lt;/code&gt; and &lt;code&gt;find_by_cond(table_name, SUBSTR(field=substr))&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  One More Thing
&lt;/h2&gt;

&lt;p&gt;One of the features of this database at the moment is it's size - its core implementation is less than 800 lines of Python code! I decided that it would be nice to highlight it in the Github description, and came up with a simple script, &lt;a href="https://github.com/ddoroshev/bazoola/blob/7f3aa725d61dc9113277258a21e0a17faa3b49e5/update_description.sh" rel="noopener noreferrer"&gt;update_description.sh&lt;/a&gt;, which I run once in a while to count the lines of &lt;code&gt;bazoola/*.py&lt;/code&gt; and update the repo description.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsg2804xzf2ylkawyw9cb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsg2804xzf2ylkawyw9cb.png" alt="Github Description" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I didn't find out yet how to run it on CI, but I think this one is already good enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I treat this update as a huge leap: a month ago Bazoola was barely usable in real-life applications, and now you can build something on top of it. There's no &lt;code&gt;VARCHAR&lt;/code&gt; or &lt;code&gt;TEXT&lt;/code&gt; fields, the amount of conditions (&lt;code&gt;EQ&lt;/code&gt;, &lt;code&gt;LT&lt;/code&gt;, &lt;code&gt;GT&lt;/code&gt;, ...) is quite limited, selecting particular fields and ordering is also not implemented, and that's why it's just a 0.0.7 release :)&lt;/p&gt;

&lt;p&gt;I like the fact that the data files contain mostly plain text, it's easy to test and debug the logic and data consistency. Of course, I'm planning to go binary and to switch from Python to C, and those benefits disappear, but by that time I'll have a solid amount of tests and a bunch of tools for managing the data.&lt;/p&gt;

&lt;p&gt;Stay tuned!&lt;/p&gt;

</description>
      <category>python</category>
      <category>database</category>
      <category>learning</category>
      <category>bazoola</category>
    </item>
    <item>
      <title>Building a Toy Database: Learning by Doing</title>
      <dc:creator>Dmitrii Doroshev</dc:creator>
      <pubDate>Wed, 16 Jul 2025 12:49:18 +0000</pubDate>
      <link>https://dev.to/pomidoroshev/building-a-toy-database-learning-by-doing-5f43</link>
      <guid>https://dev.to/pomidoroshev/building-a-toy-database-learning-by-doing-5f43</guid>
      <description>&lt;p&gt;Ever wondered how databases work under the hood? I decided to find out by building one from scratch. Meet &lt;a href="https://github.com/ddoroshev/bazoola" rel="noopener noreferrer"&gt;Bazoola&lt;/a&gt; - a simple file-based database written in pure Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Build a Toy Database?
&lt;/h2&gt;

&lt;p&gt;As a developer, I use relational databases every day, but I never truly understood what happens when I &lt;code&gt;INSERT&lt;/code&gt; or &lt;code&gt;SELECT&lt;/code&gt;. Building a database from scratch taught me more about data structures, file I/O, and system design than any tutorial ever could.&lt;/p&gt;

&lt;p&gt;Plus, it's fun to implement something that seems like magic!&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Bazoola?
&lt;/h2&gt;

&lt;p&gt;Bazoola is a lightweight, educational database that stores data in human-readable text files. It's not meant to replace SQLite or PostgreSQL - it's meant to help understand how databases work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Fixed-width column storage&lt;/li&gt;
&lt;li&gt;CRUD operations - the basics every database needs&lt;/li&gt;
&lt;li&gt;Foreign keys - because relationships matter&lt;/li&gt;
&lt;li&gt;Automatic space management - reuses deleted record space&lt;/li&gt;
&lt;li&gt;Human-readable files - you can literally &lt;code&gt;cat&lt;/code&gt; your data&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Core Idea: Fixed-Width Records
&lt;/h2&gt;

&lt;p&gt;Unlike CSV files where records have variable length, Bazoola uses fixed-width fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1     Alice         25    
2     Bob           30    
3     Charlie       35    
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes it trivial to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Jump to any record by calculating its offset&lt;/li&gt;
&lt;li&gt;Update records in-place without rewriting the file&lt;/li&gt;
&lt;li&gt;Build simple indexes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Show Me the Code!
&lt;/h2&gt;

&lt;p&gt;Here's how you use Bazoola:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bazoola&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="c1"&gt;# Define schemas
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TableAuthors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;authors&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;PK&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TableBooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;books&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;PK&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;author_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;FK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;authors&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;year&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Create database instance and the files in `data/` subdir
&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;TableAuthors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TableBooks&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Insert data
&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;authors&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;John Doe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;books&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;My Book&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;author_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;year&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2024&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# Query with joins
&lt;/span&gt;&lt;span class="n"&gt;book_with_author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;books&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;joins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;author_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;author&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;authors&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book_with_author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Output: {'id': 1, 'title': 'My Book', 'author_id': 1, 'year': 2024, 'author': {'id': 1, 'name': 'John Doe'}}
&lt;/span&gt;
&lt;span class="c1"&gt;# Close the database
&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Interesting Implementation Details
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Space Management
&lt;/h3&gt;

&lt;p&gt;When you delete a record, Bazoola fills it out with &lt;code&gt;*&lt;/code&gt; symbols, maintaining a stack of free positions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ...
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delete_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# ...
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rownum&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;row_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rownum_index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;row_size&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;free_rownums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rownum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ...
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;seek_insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;rownum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;free_rownums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rownum&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rownum&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;row_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SEEK_END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simple approach prevents the database from growing indefinitely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Indexing
&lt;/h3&gt;

&lt;p&gt;Every table automatically gets an index on its primary key in &lt;code&gt;&amp;lt;table_name&amp;gt;__id.idx.dat&lt;/code&gt; file, so finding a record by ID is O(1) - just look up the position and seek!&lt;/p&gt;

&lt;h3&gt;
  
  
  Foreign Keys
&lt;/h3&gt;

&lt;p&gt;Bazoola supports relationships between tables and joins:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Query with joins
&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;books&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;joins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;author_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;author&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;authors&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Inverse joins (one-to-many)
&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;authors&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;joins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;InverseJoin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;author_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;books&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;books&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, it doesn't enforce referencial integrity yet, and you can basically delete anything you want ¯\_(ツ)_/¯ &lt;/p&gt;

&lt;h3&gt;
  
  
  Human-Readable Storage
&lt;/h3&gt;

&lt;p&gt;Data files are just formatted text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;data/books.dat
1     My Book             1     2024
&lt;span class="k"&gt;************************************&lt;/span&gt;
3     My Book3            1     2024
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great for debugging and understanding what's happening!&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Data format and file-based storages are tricky, that's why it's all just test-based.&lt;/li&gt;
&lt;li&gt;Fixed-width has trade-offs - simple implementation, but wastes space. Real databases use more sophisticated formats.&lt;/li&gt;
&lt;li&gt;Indexes are magic: the difference between O(n) table scan and O(1) index lookup is massive, even with small datasets.&lt;/li&gt;
&lt;li&gt;Joins are hard.&lt;/li&gt;
&lt;li&gt;Testing is crucial. Database bugs can corrupt data, and comprehensive tests saved me many times.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Limitations (and why they're OK)
&lt;/h2&gt;

&lt;p&gt;The current implementation is intentionally simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no transactions (what if it fails mid-write?)&lt;/li&gt;
&lt;li&gt;no concurrency (single-threaded only)&lt;/li&gt;
&lt;li&gt;no query optimizer (every operation is naive)&lt;/li&gt;
&lt;li&gt;no B-tree indices (just a simple index for primary keys)&lt;/li&gt;
&lt;li&gt;no SQL (just Python API)&lt;/li&gt;
&lt;li&gt;fixed-width wastes space
These aren't bugs - they're learning opportunities! Each limitation is a rabbit hole to explore.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It Yourself!
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;bazoola
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://github.com/ddoroshev/bazoola" rel="noopener noreferrer"&gt;code is on GitHub&lt;/a&gt;. It's ~700 lines of Python (&lt;a href="https://github.com/ddoroshev/bazoola/blob/51e262b7b819f6820cd853cb1303c196336e8310/bazoola/db.py" rel="noopener noreferrer"&gt;db.py&lt;/a&gt;) - small enough to read in one sitting!&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;Building Bazoola opened my eyes to database internals. Some ideas for further versions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TEXT&lt;/code&gt; fields with a separate storage&lt;/li&gt;
&lt;li&gt;strict foreign key constrainsts&lt;/li&gt;
&lt;li&gt;unique constraints&lt;/li&gt;
&lt;li&gt;B-tree indexes for range queries&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ALTER TABLE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;simple query planner&lt;/li&gt;
&lt;li&gt;pure C implementation with Python bindings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But honestly, even this simple version taught me a ton.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building a toy database is one of the best learning projects I've done. It's complex enough to be challenging but simple enough to finish. You'll understand your "real" database better, and you might even have fun!&lt;/p&gt;

</description>
      <category>database</category>
      <category>learning</category>
      <category>backend</category>
      <category>python</category>
    </item>
    <item>
      <title>The Cartesian Product Trap in Django ORM</title>
      <dc:creator>Dmitrii Doroshev</dc:creator>
      <pubDate>Wed, 09 Jul 2025 21:16:23 +0000</pubDate>
      <link>https://dev.to/pomidoroshev/the-cartesian-product-trap-in-django-orm-4ldc</link>
      <guid>https://dev.to/pomidoroshev/the-cartesian-product-trap-in-django-orm-4ldc</guid>
      <description>&lt;p&gt;I hit a performance wall at work recently after adding a single extra &lt;code&gt;Count()&lt;/code&gt; on a Django queryset that looked harmless. Luckily, the problematic code didn't make it into the production environment, but I had to think hard to figure out what went wrong and find a solution.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Demo Models
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;All the following models and entities are made up by me to illustrate the problem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Imagine we have a PostgreSQL database storing information about separate stores, their departments, employees and products of the stores:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1dfmspqirod7l6y9nro5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1dfmspqirod7l6y9nro5.png" alt="Database schema showing Store, Department, Employee, and Product relationships" width="800" height="881"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The DB tables can contain much more fields, but in our case we only care about relations between the models.&lt;/p&gt;

&lt;p&gt;So in Django, &lt;code&gt;models.py&lt;/code&gt; would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CASCADE&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ...
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Department&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;departments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ...
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;department&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Department&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;employees&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ...
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;products&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seed data (roughly what bit me):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2 stores,&lt;/li&gt;
&lt;li&gt;10 departments per store,&lt;/li&gt;
&lt;li&gt;500 employees per department - 10 000 employees overall,&lt;/li&gt;
&lt;li&gt;2500 products per store.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In one of the places of our system, we show a list of the stores, including the amount of employees in each store. Pretty easy with Django ORM, right?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;stores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;total_employees&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;departments__employees&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# ...
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total_employees&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query is relatively fast and works like a charm.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Now let's imagine we were asked to add another counter: a number of products per store. We already have &lt;code&gt;total_employees&lt;/code&gt;, why not just add &lt;code&gt;total_products&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Well, most likely we already have some unit test for our piece of code, which checks the logic on a small amount of data, and before releasing the code, we can figure out that another &lt;code&gt;JOIN&lt;/code&gt; was added, some data is duplicated, and instead of just &lt;code&gt;COUNT(...)&lt;/code&gt;, we switch to &lt;code&gt;COUNT(DISTINCT ...)&lt;/code&gt;, eventually coming up with something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;stores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;total_employees&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;departments__employees&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="n"&gt;distinct&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;total_products&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;products&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;distinct&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# ...
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total_employees&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total_products&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks safe to commit, push, wait for the green tests on CI, merge and deploy.&lt;/p&gt;

&lt;p&gt;However, after the deployment you'll almost immediately see that everything hangs. As I said, there's not that many stores, only 2 for now, not that many employees, and not that many departments.&lt;/p&gt;

&lt;p&gt;And it takes, like, 10 seconds to fetch the numbers! What's wrong with it?&lt;/p&gt;

&lt;p&gt;Let's take a closer look at the generated SQL for this seemingly innocent queryset:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="nv"&gt;"shop_store"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="nv"&gt;"shop_product"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"total_products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="nv"&gt;"shop_employee"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"total_employees"&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"shop_store"&lt;/span&gt;
  &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;OUTER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="nv"&gt;"shop_product"&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"shop_store"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"shop_product"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"store_id"&lt;/span&gt;
  &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;OUTER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="nv"&gt;"shop_department"&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"shop_store"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"shop_department"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"store_id"&lt;/span&gt;
  &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;OUTER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="nv"&gt;"shop_employee"&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"shop_department"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"shop_employee"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"department_id"&lt;/span&gt; 
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nv"&gt;"shop_store"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And let's check the actual query plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GroupAggregate ... rows=2 ...
  -&amp;gt;  Nested Loop Left Join ... rows=25000000 ...
  ...
...
Execution Time: 11376.072 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Translation: these three &lt;code&gt;JOIN&lt;/code&gt;s turn into a 25-million-row cartesian mess before &lt;code&gt;GROUP BY&lt;/code&gt; and &lt;code&gt;COUNT(DISTINCT)&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Products × Departments × Employees
= 2500 × 10 × 500
= 12 500 000  (per store)
× 2 stores
= 25 000 000 joined rows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;There are multiple ways of handling this case, but the easiest fix is to use subqueries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;subquery_products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Subquery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;OuterRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;store_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;output_field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;subquery_employees&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Subquery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Employee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;department__store_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;OuterRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;department__store_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;output_field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;stores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;total_products&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subquery_products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;total_employees&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subquery_employees&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total_products&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total_employees&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SQL query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"shop_store"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;
         &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;U0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"count"&lt;/span&gt;  
         &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"shop_product"&lt;/span&gt; &lt;span class="n"&gt;U0&lt;/span&gt;  
         &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;U0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"store_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"shop_store"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;  
         &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;U0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"store_id"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
       &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"total_products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;
         &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;U0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"count"&lt;/span&gt;
         &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"shop_employee"&lt;/span&gt; &lt;span class="n"&gt;U0&lt;/span&gt;
           &lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="nv"&gt;"shop_department"&lt;/span&gt; &lt;span class="n"&gt;U1&lt;/span&gt;
             &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;U0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"department_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;U1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;
         &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;U1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"store_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"shop_store"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;
         &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;U1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"store_id"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
       &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"total_employees"&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"shop_store"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now this one takes a couple of milliseconds with pretty modest and predictable plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Seq Scan on shop_store ... rows=2 ...
  SubPlan 1
     -&amp;gt; Seq Scan on shop_product u0 ... rows=2500 ...
  SubPlan 2
     -&amp;gt; Hash Join ... rows=5000 ...
        -&amp;gt; Seq Scan on shop_employee u0_1 ... rows=10000 ...
        -&amp;gt; Hash ... rows=10 ...
...
Execution Time: 5.600 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No giant intermediate data sets, just two tiny scans:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;before: 11 376 ms (~11 seconds)&lt;/li&gt;
&lt;li&gt;after: 5.6 ms (2000x faster)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;COUNT(DISTINCT)&lt;/code&gt; with multi-branch &lt;code&gt;LEFT JOIN&lt;/code&gt;s makes the database loop through the entire cartesian product.&lt;/li&gt;
&lt;li&gt;Correlated subqueries aggregate each branch separately and scale linearly with data size.&lt;/li&gt;
&lt;li&gt;Always test aggregate queries against production-sized data before you ship.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>django</category>
      <category>sql</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Test inheritance in unittest sounds neat - until it breaks your IDE, hides failures in CI, and kills debuggability. I wrote up why it's a trap and what to do instead.</title>
      <dc:creator>Dmitrii Doroshev</dc:creator>
      <pubDate>Mon, 23 Jun 2025 11:15:31 +0000</pubDate>
      <link>https://dev.to/pomidoroshev/test-inheritance-in-unittest-sounds-neat-until-it-breaks-your-ide-hides-failures-in-ci-and-k94</link>
      <guid>https://dev.to/pomidoroshev/test-inheritance-in-unittest-sounds-neat-until-it-breaks-your-ide-hides-failures-in-ci-and-k94</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/pomidoroshev/the-hidden-cost-of-test-inheritance-1k5o" class="crayons-story__hidden-navigation-link"&gt;The Hidden Cost of Test Inheritance&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/pomidoroshev" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F700723%2F7d8bc25b-3f5e-4411-8574-740ac4adb850.jpeg" alt="pomidoroshev profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/pomidoroshev" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Dmitrii Doroshev
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Dmitrii Doroshev
                
              
              &lt;div id="story-author-preview-content-2616148" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/pomidoroshev" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F700723%2F7d8bc25b-3f5e-4411-8574-740ac4adb850.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Dmitrii Doroshev&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/pomidoroshev/the-hidden-cost-of-test-inheritance-1k5o" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 23 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/pomidoroshev/the-hidden-cost-of-test-inheritance-1k5o" id="article-link-2616148"&gt;
          The Hidden Cost of Test Inheritance
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/python"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;python&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/testing"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;testing&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/unittest"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;unittest&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/pytest"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;pytest&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/pomidoroshev/the-hidden-cost-of-test-inheritance-1k5o#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>python</category>
      <category>testing</category>
      <category>unittest</category>
      <category>pytest</category>
    </item>
    <item>
      <title>The Hidden Cost of Test Inheritance</title>
      <dc:creator>Dmitrii Doroshev</dc:creator>
      <pubDate>Mon, 23 Jun 2025 08:45:51 +0000</pubDate>
      <link>https://dev.to/pomidoroshev/the-hidden-cost-of-test-inheritance-1k5o</link>
      <guid>https://dev.to/pomidoroshev/the-hidden-cost-of-test-inheritance-1k5o</guid>
      <description>&lt;p&gt;I'm subscribed to Adam Johnson's blog and usually really enjoy his writing - it's practical, deep, and no-bullshit. But one recent post, &lt;a href="https://adamj.eu/tech/2025/05/30/python-unittest-common-tests/" rel="noopener noreferrer"&gt;Python: sharing common tests in unittest&lt;/a&gt;, caught me off guard.&lt;/p&gt;

&lt;p&gt;It describes a "neat" pattern: write reusable test logic in a base class, subclass it to test multiple objects, hiding the base class from &lt;code&gt;unittest&lt;/code&gt; discovery. While the intent is fine - DRYing out duplicated test code - the result is fragile, confusing, and just not worth it.&lt;/p&gt;

&lt;p&gt;Here's why.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pattern: DRY Tests via Subclassing
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Sample units to test
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Armadillo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hrrr!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Okapi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Gronk!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Test module
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BaseAnimalTests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;animal_class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_speak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;sound&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animal_class&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIsInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sound&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertGreater&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sound&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArmadilloTests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseAnimalTests&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;animal_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Armadillo&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OkapiTests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseAnimalTests&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;animal_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Okapi&lt;/span&gt;

&lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;BaseAnimalTests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, it works and it reduces duplication. But it comes at the cost of everything else that makes tests maintainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problems
&lt;/h2&gt;

&lt;h3&gt;
  
  
  IDE and DX Pain
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdbmlck4esgykvcogrf2b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdbmlck4esgykvcogrf2b.png" alt="IDE" width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When a test fails, I want to jump to it in my IDE, set a breakpoint, and debug. With this pattern - good luck.&lt;/p&gt;

&lt;p&gt;The method doesn't exist in &lt;code&gt;ArmadilloTests&lt;/code&gt;, it's buried in a deleted parent class. You have to manually hunt it down, re-declare the test method just to put a breakpoint and debug it, and pray the &lt;code&gt;animal_class&lt;/code&gt; setup matches what failed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArmadilloTests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;animal_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Armadillo&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_speak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;test_speak&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0am6hlpfeggv0nag9uku.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0am6hlpfeggv0nag9uku.png" alt="breakpoint" width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's tedious and wastes time. All this to avoid writing a 3-line test twice?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArmadilloTests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_speak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;sound&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Armadillo&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIsInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sound&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertGreater&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sound&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clear, simple, debug-friendly. Worth the few extra lines.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI Failures Are Confusing
&lt;/h3&gt;

&lt;p&gt;If a shared test fails in CI, you get something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test_speak (tests.ArmadilloTests.test_speak) ... FAIL
...
Traceback (most recent call last):
  File ".../tests.py", line 20, in test_speak
    self.assertGreater(len(sound), 0)
AssertionError: 0 not greater than 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the method isn't defined in &lt;code&gt;ArmadilloTests&lt;/code&gt;, and &lt;a href="https://www.jetbrains.com/help/pycharm/searching-everywhere.html#search_all" rel="noopener noreferrer"&gt;Search everywhere&lt;/a&gt; won't help at all:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjprp24y5pvnv5g1u5jhk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjprp24y5pvnv5g1u5jhk.png" alt="nothing found" width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So now you have to reverse-engineer which base class it came from and how to recreate it locally.&lt;/p&gt;

&lt;p&gt;This isn't clever. It's just fragile.&lt;/p&gt;

&lt;h2&gt;
  
  
  When It &lt;em&gt;Kinda&lt;/em&gt; Makes Sense
&lt;/h2&gt;

&lt;p&gt;There are rare cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dozens of classes implementing the same interface&lt;/li&gt;
&lt;li&gt;you're the only one maintaining the codebase&lt;/li&gt;
&lt;li&gt;you run everything headless in CI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But even then, you're building test framework plumbing to save what, a hundred lines?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Clean Alternative: Parametrize It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pytest Style
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@pytest.mark.parametrize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;animal_class&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Armadillo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Okapi&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_speak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animal_class&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;sound&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;animal_class&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sound&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You see all the parameters. You see where the test lives. Failures are explicit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test_speak[Armadillo] FAILED
test_speak[Okapi] PASSED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can re-run just the failing test. You can debug with a conditional breakpoint. You don't need to explain how the tests are wired together - because they're not.&lt;/p&gt;

&lt;h3&gt;
  
  
  unittest Style (Optional, Not Ideal)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;parameterized&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;parameterized_class&lt;/span&gt;

&lt;span class="nd"&gt;@parameterized_class&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;animal_class&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Armadillo&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;animal_class&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Okapi&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;class_name_func&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;get_class_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AnimalTests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_speak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;sound&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animal_class&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIsInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sound&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertGreater&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sound&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;parameterized_class&lt;/code&gt; from &lt;a href="https://pypi.org/project/parameterized/" rel="noopener noreferrer"&gt;parameterized&lt;/a&gt; is still better than inheritance, but clunkier. Output is readable if you customize &lt;code&gt;class_name_func&lt;/code&gt;. IDE support isn't great. Pytest remains the better option for anything dynamic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Verdict
&lt;/h2&gt;

&lt;p&gt;Tests should fail clearly, debug easily, and be readable years later. This pattern fails all three.&lt;/p&gt;

&lt;p&gt;DRY is good. But in tests, visible duplication beats invisible abstraction.&lt;/p&gt;

&lt;p&gt;Adam's trick technically works, but in practice, it makes tests harder to navigate, harder to trust, and harder to work with.&lt;/p&gt;

&lt;p&gt;Stick to the boring version - you'll thank yourself later.&lt;/p&gt;

</description>
      <category>python</category>
      <category>testing</category>
      <category>unittest</category>
      <category>pytest</category>
    </item>
    <item>
      <title>Why Django's override_settings Sometimes Fails (and How reload + patch Saved Me)</title>
      <dc:creator>Dmitrii Doroshev</dc:creator>
      <pubDate>Fri, 13 Jun 2025 08:11:39 +0000</pubDate>
      <link>https://dev.to/pomidoroshev/why-djangos-overridesettings-sometimes-fails-and-how-reload-patch-saved-me-3el9</link>
      <guid>https://dev.to/pomidoroshev/why-djangos-overridesettings-sometimes-fails-and-how-reload-patch-saved-me-3el9</guid>
      <description>&lt;p&gt;Sometimes &lt;code&gt;@override_settings&lt;/code&gt; just doesn’t cut it.&lt;/p&gt;

&lt;p&gt;I ran into a nasty issue while testing a Django module that relies on global state initialized during import. The usual test approach didn’t work. Here’s what happened and how I solved it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;We had a module that builds a global dictionary from Django settings &lt;em&gt;at import time&lt;/em&gt;. Let’s call it &lt;code&gt;dragon.py&lt;/code&gt;, which takes &lt;code&gt;settings.PUT_EGGS&lt;/code&gt;, which is &lt;code&gt;False&lt;/code&gt; by default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;

&lt;span class="n"&gt;DRAGON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PUT_EGGS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;DRAGON&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eggs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spam&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another module uses &lt;code&gt;DRAGON&lt;/code&gt; for core logic, e.g. &lt;code&gt;mario.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;myproject.dragon&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DRAGON&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_eggs&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eggs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;DRAGON&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Found eggs!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Eggs not found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I wanted to write a test that tweaks &lt;code&gt;DRAGON&lt;/code&gt; and expects the logic to behave differently. Easy, right?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@override_settings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PUT_EGGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_find_eggs&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;find_eggs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Found eggs!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wrong. The test failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;override_settings&lt;/code&gt; works, but only for code that reads settings &lt;em&gt;at runtime&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In my case, &lt;code&gt;DRAGON&lt;/code&gt; was already built &lt;em&gt;at import time&lt;/em&gt; , before the override kicked in. So it used the old value of &lt;code&gt;PUT_EGGS&lt;/code&gt;, no matter what I did in the test.&lt;/p&gt;

&lt;p&gt;This is the classic trap of global state baked during import. Welcome to pain town.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix: reload + patch
&lt;/h2&gt;

&lt;p&gt;Here's how I got out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;importlib&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.test&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;override_settings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unittest.mock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;myproject.mario&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;find_eggs&lt;/span&gt;

&lt;span class="nd"&gt;@override_settings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PUT_EGGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_find_eggs&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Reload the dragon module so DRAGON is rebuilt
&lt;/span&gt;    &lt;span class="c1"&gt;# with updated settings
&lt;/span&gt;    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;myproject&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dragon&lt;/span&gt;
    &lt;span class="n"&gt;new_dragon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;importlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dragon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Patch the logic module to use the reloaded DRAGON
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;myproject.mario.DRAGON&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_dragon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DRAGON&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;find_eggs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Found eggs!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why This Works
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;importlib.reload(dragon)&lt;/code&gt; forces a fresh import of &lt;code&gt;dragon&lt;/code&gt;, rebuilding &lt;code&gt;DRAGON&lt;/code&gt; with the overridden settings;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dragon.DRAGON&lt;/code&gt; is updated in the scope of the test only, i.e. &lt;code&gt;mario&lt;/code&gt; module still has the stale version of &lt;code&gt;DRAGON;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;patch(...)&lt;/code&gt; solves this problem by swapping the old &lt;code&gt;DRAGON&lt;/code&gt; in &lt;code&gt;mario&lt;/code&gt; with the freshly rebuilt one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is surgical. Ugly, but effective.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Avoid putting non-trivial logic at module scope, especially if it depends on Django settings. Wrap it in a function or lazy loader.&lt;/li&gt;
&lt;li&gt;If you're stuck with global state, &lt;code&gt;reload()&lt;/code&gt; and &lt;code&gt;patch()&lt;/code&gt; give you a way out - just be careful about cascading dependencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’ve ever had a test mysteriously fail after overriding settings, this might be why.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>testing</category>
      <category>reload</category>
    </item>
    <item>
      <title>Calculating the next run date of a Celery periodic task</title>
      <dc:creator>Dmitrii Doroshev</dc:creator>
      <pubDate>Thu, 12 Jun 2025 06:28:07 +0000</pubDate>
      <link>https://dev.to/pomidoroshev/calculating-the-next-run-date-of-a-celery-periodic-task-1129</link>
      <guid>https://dev.to/pomidoroshev/calculating-the-next-run-date-of-a-celery-periodic-task-1129</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;You have a periodic task in Celery defined with a &lt;code&gt;crontab(...)&lt;/code&gt; &lt;a href="https://docs.celeryq.dev/en/stable/userguide/periodic-tasks.html#crontab-schedules" rel="noopener noreferrer"&gt;schedule&lt;/a&gt;, and you want to calculate the next time it's supposed to run.&lt;/p&gt;

&lt;p&gt;Example: you want to find out when &lt;code&gt;crontab(hour=12, minute=0)&lt;/code&gt; will trigger next after now.&lt;/p&gt;

&lt;p&gt;Simple, right? There’s &lt;a href="https://github.com/pallets-eco/croniter" rel="noopener noreferrer"&gt;croniter&lt;/a&gt; library, which seems to be designed to solve this exact problem. Just use it, right?&lt;/p&gt;

&lt;p&gt;Well.&lt;/p&gt;

&lt;h2&gt;
  
  
  First mistake: trying croniter with crontab
&lt;/h2&gt;

&lt;p&gt;So my first instinct was to use &lt;code&gt;croniter&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery.schedules&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;crontab&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;croniter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;croniter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="n"&gt;schedule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;crontab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cron&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;croniter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;next_run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Boom 💥 doesn’t work. Because Celery’s &lt;code&gt;crontab&lt;/code&gt; is &lt;em&gt;not&lt;/em&gt; a string and &lt;code&gt;croniter&lt;/code&gt; expects a string like &lt;code&gt;"0 12 * * *"&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AttributeError: 'crontab' object has no attribute 'lower'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And no, &lt;code&gt;crontab()&lt;/code&gt; does not have a nice &lt;code&gt;.as_cron_string()&lt;/code&gt; method either.&lt;/p&gt;

&lt;p&gt;So now you’re stuck parsing &lt;code&gt;crontab&lt;/code&gt;'s internal fields (&lt;code&gt;._orig_minute&lt;/code&gt;, &lt;code&gt;._orig_hour&lt;/code&gt;, etc) just to reconstruct a string - and it starts to smell like overengineering for something that should be simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  The right way (which I learned too late)
&lt;/h2&gt;

&lt;p&gt;Turns out Celery’s &lt;code&gt;crontab&lt;/code&gt; (and all schedules derived from &lt;code&gt;celery.schedules.BaseSchedule&lt;/code&gt;) already has a method for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery.schedules&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;crontab&lt;/span&gt;

&lt;span class="n"&gt;schedule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;crontab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# `now` is datetime.datetime(2025, 6, 11, 0, 16, 58, 484085)
&lt;/span&gt;&lt;span class="n"&gt;next_run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remaining_delta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# `next_run` is datetime.datetime(2025, 6, 11, 12, 0)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it. You don’t need &lt;code&gt;croniter&lt;/code&gt; at all. Celery knows how to calculate the delta to the next run. It just doesn’t shout about it in &lt;a href="https://docs.celeryq.dev/en/stable/reference/celery.schedules.html#celery.schedules.crontab.remaining_delta" rel="noopener noreferrer"&gt;the docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;don’t reinvent Celery’s scheduling logic - it already has what you need;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;crontab&lt;/code&gt; is not a cron string, don’t try to treat it like one;&lt;/li&gt;
&lt;li&gt;use &lt;code&gt;.remaining_delta(last_run)&lt;/code&gt; to calculate when the next run will happen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hope this saves someone the 2 hours I wasted trying to do it the wrong way.&lt;/p&gt;

</description>
      <category>python</category>
      <category>backend</category>
      <category>celery</category>
      <category>programming</category>
    </item>
    <item>
      <title>Pytest Fish shell autocompletion</title>
      <dc:creator>Dmitrii Doroshev</dc:creator>
      <pubDate>Sun, 29 Dec 2024 23:04:10 +0000</pubDate>
      <link>https://dev.to/pomidoroshev/pytest-fish-shell-autocompletion-58o6</link>
      <guid>https://dev.to/pomidoroshev/pytest-fish-shell-autocompletion-58o6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;sup&gt;TL;DR&lt;/sup&gt; &lt;a href="https://github.com/ddoroshev/pytest.fish" rel="noopener noreferrer"&gt;https://github.com/ddoroshev/pytest.fish&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Typing repetitive commands or copying and pasting test names can eat up valuable time. To help, I've created &lt;code&gt;pytest.fish&lt;/code&gt; - a &lt;a href="https://fishshell.com/" rel="noopener noreferrer"&gt;Fish shell&lt;/a&gt; plugin that simplifies your pytest workflow. It's lightweight, simple to set up, and makes testing more efficient.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Use
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Autocomplete test paths
&lt;/h4&gt;

&lt;p&gt;Type &lt;code&gt;pytest&lt;/code&gt; and hit &lt;code&gt;TAB&lt;/code&gt; to get suggestions for test paths and functions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1nglmbxaw3hoslo2imtr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1nglmbxaw3hoslo2imtr.png" width="800" height="155"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Support for &lt;code&gt;-k&lt;/code&gt; filter
&lt;/h4&gt;

&lt;p&gt;Narrow down tests with &lt;code&gt;-k&lt;/code&gt; and get name suggestions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffseaild1gn8g1aw1rc3o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffseaild1gn8g1aw1rc3o.png" width="800" height="159"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The plugin dynamically scans your project, so suggestions stay up-to-date.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;Install with &lt;a href="https://github.com/jorgebucaran/fisher" rel="noopener noreferrer"&gt;Fisher&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fisher install ddoroshev/pytest.fish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or manually copy the files from the &lt;a href="https://github.com/ddoroshev/pytest.fish" rel="noopener noreferrer"&gt;repository&lt;/a&gt; into your Fish configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;p&gt;The plugin doesn't rely on &lt;code&gt;pytest&lt;/code&gt; directly (yet). Instead, it scans the current directory for test files and searches for test functions inside them, making the process relatively fast and efficient.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other shells?
&lt;/h3&gt;

&lt;p&gt;Since I primarily use Fish in my local development environment, I created a plugin specifically for this shell. However, if you use Bash or Zsh, feel free to create your own - or switch to Fish already. 😉&lt;/p&gt;

</description>
      <category>python</category>
      <category>pytest</category>
      <category>shell</category>
      <category>fish</category>
    </item>
    <item>
      <title>This Week in Changelogs: flask, pytest, IPython, etc</title>
      <dc:creator>Dmitrii Doroshev</dc:creator>
      <pubDate>Tue, 07 Mar 2023 21:48:13 +0000</pubDate>
      <link>https://dev.to/pomidoroshev/this-week-in-changelogs-flask-pytest-ipython-etc-58la</link>
      <guid>https://dev.to/pomidoroshev/this-week-in-changelogs-flask-pytest-ipython-etc-58la</guid>
      <description>&lt;h3&gt;
  
  
  pyenv 2.3.13, 2.3.14
&lt;/h3&gt;

&lt;p&gt;Highlights from the &lt;a href="https://github.com/pyenv/pyenv/blob/master/CHANGELOG.md#release-2314" rel="noopener noreferrer"&gt;changelog&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;added versions 3.10.10, 3.11.2, and 3.12.0a5;&lt;/li&gt;
&lt;li&gt;fixed versions 3.5.10 and 3.6.15 for macOS and modern 64-bit platforms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This one made me laugh a bit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/pyenv/pyenv/pull/2612/commits/a7b181c3caf98aa1aed224b02b5c3c3da108241f#diff-a7f5a90016571f823f0fee81a3d70422bf945159513eaf62c3613b9addffac8aL160-R165" rel="noopener noreferrer"&gt;a7b181c&lt;/a&gt; introduce an indentation issue&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pyenv/pyenv/pull/2620/commits/382974214d5f36158f2bd381dbacae5449708a04" rel="noopener noreferrer"&gt;3829742&lt;/a&gt; fix the indendation issue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's how programming actually works!&lt;/p&gt;

&lt;p&gt;TIL: &lt;code&gt;head -n123&lt;/code&gt; is a part of POSIX, &lt;code&gt;head -123&lt;/code&gt; is a shorthand that can be missing in some operating systems (&lt;a href="https://github.com/pyenv/pyenv/pull/2629" rel="noopener noreferrer"&gt;pull request&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  IPython 8.11.0
&lt;/h3&gt;

&lt;p&gt;Highlights from the &lt;a href="https://ipython.readthedocs.io/en/stable/whatsnew/version8.html#ipython-8-11" rel="noopener noreferrer"&gt;changelog&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;%autoreload&lt;/code&gt; supports meaningful parameters (&lt;code&gt;%autoreload all&lt;/code&gt;, &lt;code&gt;%autoreload off&lt;/code&gt;, etc), not only numbers (&lt;code&gt;%autoreload 0&lt;/code&gt;, &lt;code&gt;%autoreload 2&lt;/code&gt;, etc).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I like the log of the &lt;a href="https://github.com/ipython/ipython/pull/13774" rel="noopener noreferrer"&gt;pull request&lt;/a&gt;, it illustrates the approach of implementing a feature step-by-step, one frame at a time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/ipython/ipython/pull/13774/commits/a7c76db7d6dbb961af374b2f34c996eaef5ad62b#diff-776d8acae1dbb42ec4f06629266b825f6eae2cff3da3e6c0ca270b8c9335c8d8R555-R567" rel="noopener noreferrer"&gt;a7c76db&lt;/a&gt; get a dumb and working solution&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ipython/ipython/pull/13774/commits/d8bb14b60cce68058769dec4bb2a9e99b33d7150#diff-776d8acae1dbb42ec4f06629266b825f6eae2cff3da3e6c0ca270b8c9335c8d8L575-L588" rel="noopener noreferrer"&gt;d8bb14b&lt;/a&gt; optimize it&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ipython/ipython/pull/13774/commits/809ebdf7fe6b1a27de4f9a4417441b6814468666#diff-776d8acae1dbb42ec4f06629266b825f6eae2cff3da3e6c0ca270b8c9335c8d8R616-R633" rel="noopener noreferrer"&gt;809ebdf&lt;/a&gt; and make it nice &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, &lt;a href="https://github.com/ipython/ipython/pull/13774/files#diff-776d8acae1dbb42ec4f06629266b825f6eae2cff3da3e6c0ca270b8c9335c8d8R611-R629" rel="noopener noreferrer"&gt;this fragment&lt;/a&gt; is quite interesting, &lt;code&gt;print&lt;/code&gt; and &lt;code&gt;logger.info&lt;/code&gt; need to be used carefully for logging and protected from being overwritten during hot-reload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;print&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;autoreload&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;p&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;l&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything you wanted to know about GitHub actions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqlxovnqxs10ctjpp60w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqlxovnqxs10ctjpp60w.png" alt="GitHub discussion" width="800" height="645"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  flask 2.2.3
&lt;/h3&gt;

&lt;p&gt;Although the &lt;a href="https://flask.palletsprojects.com/en/2.2.x/changes/#version-2-2-3" rel="noopener noreferrer"&gt;changelog&lt;/a&gt; is not that big, I like the thing about &lt;code&gt;flask run --debug&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/pallets/flask/issues/4777#issuecomment-1221500330" rel="noopener noreferrer"&gt;Previously&lt;/a&gt;, it was &lt;code&gt;flask --debug run&lt;/code&gt; , and it was awkward. The fix itself is &lt;a href="https://github.com/pallets/flask/pull/4779/files#diff-fed8939d1905b99bba605ad91e9ccdf5ede6223e03ccbc3f0121853035051e62R936" rel="noopener noreferrer"&gt;quite small&lt;/a&gt;, but there's a lot of changes in docs, and also a &lt;a href="https://github.com/pallets/flask/pull/4779/files#diff-366ddb05690615ba39d8138d6a937585c5854776c1354b6a5e00f8b729ffb41d" rel="noopener noreferrer"&gt;PyCharm screenshot&lt;/a&gt; was changed. Nice and pure!&lt;/p&gt;

&lt;h3&gt;
  
  
  pytest 7.2.1, 7.2.2
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://docs.pytest.org/en/stable/changelog.html#pytest-7-2-2-2023-03-03" rel="noopener noreferrer"&gt;changelogs&lt;/a&gt; contains mostly bug fixes. &lt;a href="https://github.com/pytest-dev/pytest/issues/10533" rel="noopener noreferrer"&gt;One of them&lt;/a&gt; is about &lt;code&gt;pytest.approx()&lt;/code&gt; causing &lt;code&gt;ZeroDivisionError&lt;/code&gt; on dicts. &lt;/p&gt;

&lt;p&gt;Another one fixes type checkers behaviour for the &lt;a href="https://github.com/pytest-dev/pytest/pull/10660/files#diff-529eab7698270aede4147aa79fbb77b42c135c4767aa68ec35173504a7389cd9R33" rel="noopener noreferrer"&gt;following code&lt;/a&gt;, which I think should be illegal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;contextlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nullcontext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;excinfo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Please, don't write the code like this.)&lt;/p&gt;

&lt;p&gt;And they &lt;a href="https://github.com/pytest-dev/pytest/pull/10607/files#diff-40c29b1f1136f0a212e98384b44450adaf2c8808fdf14084d4ce78d63dc9d866R649" rel="noopener noreferrer"&gt;fixed&lt;/a&gt; a race condition when creating directories in parallel, using &lt;code&gt;os.makedirs(..., exists_ok=True)&lt;/code&gt;. Simple, but helpful.&lt;/p&gt;

&lt;h3&gt;
  
  
  whitenoise 6.4.0
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/evansd/whitenoise/blob/main/docs/changelog.rst#640-2023-02-25" rel="noopener noreferrer"&gt;changelog&lt;/a&gt; mentions support for Django 4.2. It was good to know, by the way, that &lt;code&gt;STATICFILES_STORAGE&lt;/code&gt; is &lt;a href="https://docs.djangoproject.com/en/dev/releases/4.2/#custom-file-storages" rel="noopener noreferrer"&gt;going to be changed&lt;/a&gt; to &lt;code&gt;STORAGES&lt;/code&gt; dict (&lt;a href="https://github.com/evansd/whitenoise/pull/464/files" rel="noopener noreferrer"&gt;pull request&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  django-cors-headers 3.14.0
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/adamchainz/django-cors-headers/blob/main/CHANGELOG.rst#3140-2023-02-25" rel="noopener noreferrer"&gt;Changelog&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;added support for Django 4.2,&lt;/li&gt;
&lt;li&gt;switched from &lt;code&gt;urlparse&lt;/code&gt; to &lt;code&gt;urlsplit&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://github.com/adamchainz/django-cors-headers/pull/793" rel="noopener noreferrer"&gt;latter&lt;/a&gt; is the most interesting, &lt;code&gt;urlsplit&lt;/code&gt; is slightly faster. Also, it's cached, so sometimes you gain a huge performance.&lt;/p&gt;

&lt;p&gt;The difference between these functions is that &lt;code&gt;urlparse&lt;/code&gt; &lt;a href="https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlparse" rel="noopener noreferrer"&gt;includes parsing&lt;/a&gt; of the "parameters" section of a URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scheme://netloc/path;parameters?query#fragment
                     ^ this
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since it's not widely used, in most cases it's safe to switch from &lt;code&gt;urlparse&lt;/code&gt; to &lt;code&gt;urlsplit&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>twic</category>
      <category>flask</category>
      <category>python</category>
      <category>todayilearned</category>
    </item>
    <item>
      <title>This Week in Changelogs: Django and faker</title>
      <dc:creator>Dmitrii Doroshev</dc:creator>
      <pubDate>Sat, 18 Feb 2023 11:02:14 +0000</pubDate>
      <link>https://dev.to/pomidoroshev/this-week-in-changelogs-django-and-faker-2n0b</link>
      <guid>https://dev.to/pomidoroshev/this-week-in-changelogs-django-and-faker-2n0b</guid>
      <description>&lt;h3&gt;
  
  
  Django 4.1.6, 4.1.7
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;4.1.6: &lt;a href="https://docs.djangoproject.com/en/4.1/releases/4.1.6/" rel="noopener noreferrer"&gt;release notes&lt;/a&gt;, &lt;a href="https://www.djangoproject.com/weblog/2023/feb/01/security-releases/" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;4.1.7: &lt;a href="https://docs.djangoproject.com/en/4.1/releases/4.1.7/" rel="noopener noreferrer"&gt;release notes&lt;/a&gt;, &lt;a href="https://www.djangoproject.com/weblog/2023/feb/14/security-releases/" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/django/django/commit/9d7bd5a56b1ce0576e8e07a8001373576d277942" rel="noopener noreferrer"&gt;9d7bd5a&lt;/a&gt;&lt;br&gt;
An interesting bug of parsing the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language" rel="noopener noreferrer"&gt;&lt;code&gt;Accept-Language&lt;/code&gt;&lt;/a&gt; header. The format of the header value is complex, so there's a bunch of &lt;a href="https://github.com/django/django/commit/9d7bd5a56b1ce0576e8e07a8001373576d277942#diff-cc1f617ac65a58aec8f73b7a909c5bb48e0391543fe64c4f7ee22d4333a529efR40-R56" rel="noopener noreferrer"&gt;regular expressions&lt;/a&gt; and &lt;code&gt;@functools.lru_cache(maxsize=1000)&lt;/code&gt; for caching the result. However, you can pass a huge header multiple times, causing DoS, so they added two &lt;code&gt;if&lt;/code&gt; statements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one that checks if the length is less than &lt;code&gt;ACCEPT_LANGUAGE_HEADER_MAX_LENGTH&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;second - for checking the comma-separated strings. So they decided not to just raise an exception or truncate the string by &lt;code&gt;[:ACCEPT_LANGUAGE_HEADER_MAX_LENGTH]&lt;/code&gt;, but truncate the value in a safe way, so it can be parsed in a meaningful result.
Good job!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/django/django/commit/26b7a25632e7fe0b897bc9b88b5c364a4b6398b6" rel="noopener noreferrer"&gt;26b7a25&lt;/a&gt;&lt;br&gt;
There was a &lt;a href="https://code.djangoproject.com/ticket/34291" rel="noopener noreferrer"&gt;bug&lt;/a&gt; in generated SQL, caused by that &lt;code&gt;.desc()&lt;/code&gt; in the model's &lt;code&gt;Meta.constraints&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;constraints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;UniqueConstraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unique_lower_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which resulted in &lt;code&gt;&amp;lt;...&amp;gt; WHERE LOWER("myapp_foo"."name") DESC &amp;lt;...&amp;gt;&lt;/code&gt; when checking the uniqueness. Apparently, Django can check the constraints itself, not delegating it to the underlying database.&lt;/p&gt;

&lt;p&gt;Although the fix is trivial, the case is not, and it wasn't covered in the &lt;a href="https://github.com/django/django/commit/667105877e6723c6985399803a364848891513cc" rel="noopener noreferrer"&gt;initial implementation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By the way, I like how they use &lt;a href="https://github.com/django/django/commit/26b7a25632e7fe0b897bc9b88b5c364a4b6398b6#diff-ea321d4c8efedd284e435745051f8d44b2a87e6b09788facd62d63b460432677R679" rel="noopener noreferrer"&gt;typographic double quotes&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Constraint “name_lower_uniq_desc” is violated.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/django/django/commit/a637d0bd22665edfe7af40b4da3297462ec3c9cf" rel="noopener noreferrer"&gt;a637d0b&lt;/a&gt; &lt;a href="https://github.com/django/django/commit/f3b6a4f1f3f9edbed805e62f070221a5eb163d1a" rel="noopener noreferrer"&gt;f3b6a4f&lt;/a&gt; Those &lt;code&gt;black&lt;/code&gt; updates are annoying, mainly because they make &lt;code&gt;git blame&lt;/code&gt; misleading. However, there's a solution I didn't know about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;git blame --ignore-revs-file &amp;lt;file&amp;gt;&lt;/code&gt; - &lt;a href="https://git-scm.com/docs/git-blame#Documentation/git-blame.txt---ignore-revs-fileltfilegt" rel="noopener noreferrer"&gt;ignore commits&lt;/a&gt; listed in the &lt;code&gt;file&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.git-blame-ignore-revs&lt;/code&gt; file - &lt;a href="https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view" rel="noopener noreferrer"&gt;make GitHub&lt;/a&gt; ignore them as well.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/django/django/commit/590a92e4562c8ebd3eb25dc1e878bd1e3eb758d4" rel="noopener noreferrer"&gt;590a92e&lt;/a&gt;&lt;br&gt;
The &lt;a href="https://code.djangoproject.com/ticket/34319" rel="noopener noreferrer"&gt;bug&lt;/a&gt; was caused by the &lt;a href="https://github.com/django/django/commit/667105877e6723c6985399803a364848891513cc" rel="noopener noreferrer"&gt;commit&lt;/a&gt; which we've already seen. Now you can safely raise &lt;code&gt;ValidationError&lt;/code&gt; without the &lt;code&gt;code&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/django/django/commit/628b33a854a9c68ec8a0c51f382f304a0044ec92" rel="noopener noreferrer"&gt;628b33a&lt;/a&gt;&lt;br&gt;
One more DoS fix, now it's about number of opened files when you put too many of them in one multipart payload. The fix introduces &lt;code&gt;TooManyFilesSent&lt;/code&gt; exception, which results in HTTP 400 (&lt;code&gt;DATA_UPLOAD_MAX_NUMBER_FILES = 100&lt;/code&gt; by default).&lt;/p&gt;

&lt;p&gt;I like &lt;a href="https://github.com/django/django/commit/628b33a854a9c68ec8a0c51f382f304a0044ec92#diff-701db8db8b42a48d7e02b7055a96db867d02b6f511371314ef905d74114f54ffR125-R128" rel="noopener noreferrer"&gt;this fragment&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_parse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fileobj&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;fileobj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beware of freeing your resources, garbage collector can't help you all the time!&lt;/p&gt;

&lt;h3&gt;
  
  
  faker 16.6.1..17.0.0
&lt;/h3&gt;

&lt;p&gt;Their &lt;a href="https://github.com/joke2k/faker/blob/deda2d229dbf56fcaf4e8f5ea522ad82dc365551/CHANGELOG.md" rel="noopener noreferrer"&gt;CHANGELOG&lt;/a&gt; is quite descriptive, so I'll just highlight something that I liked.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;faker&lt;/code&gt; can generate valid image URLs using specific websites (TIL), and one of them, &lt;a href="https://placeimg.com/" rel="noopener noreferrer"&gt;PlaceIMG&lt;/a&gt;, is &lt;a href="https://github.com/joke2k/faker/issues/1788" rel="noopener noreferrer"&gt;shutting down&lt;/a&gt;, and they removed it from the list. The announcement is included in all the generated images:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnkde7nj78v2eu1lg5cjk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnkde7nj78v2eu1lg5cjk.png" alt="PlaceIMG is shutting down" width="722" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Biased booleans &lt;a href="https://github.com/joke2k/faker/pull/1793" rel="noopener noreferrer"&gt;introduced&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/joke2k/faker/commit/a78228818bf7a312dd4488ff8de9ff587fe70cc7" rel="noopener noreferrer"&gt;Added &lt;code&gt;emoji&lt;/code&gt;&lt;/a&gt; provider 🎉 🥳&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/joke2k/faker/pull/1784/files" rel="noopener noreferrer"&gt;Added new &lt;code&gt;es_AR&lt;/code&gt;&lt;/a&gt; provider, but for some reason it's not in the reflected in the &lt;a href="https://github.com/joke2k/faker/blob/deda2d229dbf56fcaf4e8f5ea522ad82dc365551/CHANGELOG.md" rel="noopener noreferrer"&gt;CHANGELOG&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/joke2k/faker/commit/dc48bb5eafbb1fb3f422b07e06eedc1c61a72153" rel="noopener noreferrer"&gt;Black formatting&lt;/a&gt; - always beautiful.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition, it turned out that GitHub can put those linter errors from the actions right in the code. I don't know yet how to add this, but I definitely want it!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkjcqg1rianz3nouq85cf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkjcqg1rianz3nouq85cf.png" alt="Inline warnings in GitHub diff" width="800" height="1010"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>todayilearned</category>
      <category>twic</category>
      <category>django</category>
      <category>faker</category>
    </item>
  </channel>
</rss>
