<?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>Forem: Kelvin Wangonya</title>
    <description>The latest articles on Forem by Kelvin Wangonya (@wangonya).</description>
    <link>https://forem.com/wangonya</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%2F63723%2F5217688b-8091-43b7-8d55-8f89b6c1eeee.jpeg</url>
      <title>Forem: Kelvin Wangonya</title>
      <link>https://forem.com/wangonya</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/wangonya"/>
    <language>en</language>
    <item>
      <title>Be careful with join type typos</title>
      <dc:creator>Kelvin Wangonya</dc:creator>
      <pubDate>Thu, 07 Nov 2024 06:30:38 +0000</pubDate>
      <link>https://forem.com/wangonya/be-careful-with-join-type-typos-1k39</link>
      <guid>https://forem.com/wangonya/be-careful-with-join-type-typos-1k39</guid>
      <description>&lt;p&gt;I noticed a typo in one of my sql queries today, but the funny thing is the query still worked fine. It looked something like this:&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="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
    &lt;span class="n"&gt;table_1&lt;/span&gt;
&lt;span class="n"&gt;LEFFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt;
    &lt;span class="n"&gt;table_2&lt;/span&gt;
&lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first I thought BigQuery was pretty smart and just assumed I meant &lt;code&gt;LEFT&lt;/code&gt; instead. Maybe it's a common typo and there's an alias for it. So I got curious and tried out this:&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="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
    &lt;span class="n"&gt;table_1&lt;/span&gt;
&lt;span class="n"&gt;LEFFFFTFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt;
    &lt;span class="n"&gt;table_2&lt;/span&gt;
&lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No error. That can't be right.&lt;/p&gt;

&lt;p&gt;That's when it hit me. This query isn't doing a &lt;code&gt;LEFT JOIN&lt;/code&gt;. It's using the incorrectly spelled name as an alias for &lt;code&gt;table_1&lt;/code&gt; and doing an &lt;code&gt;INNER JOIN&lt;/code&gt; instead. Basically this:&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="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
    &lt;span class="n"&gt;table_1&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;LEFFT&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;
    &lt;span class="n"&gt;table_2&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;LEFFT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;table_2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;LEFFT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;table_2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the query works fine but the results would be incorrect.&lt;/p&gt;

&lt;p&gt;This is one merit of syntax highlighting I've never really thought about until now. The only reason I noticed this quickly was by seeing &lt;code&gt;LEFFT&lt;/code&gt; wasn't highlighted as I expected. The results of the query looked fine at first glance so this would have definitely bit me somewhere down the line if it got to production.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>todayilearned</category>
    </item>
    <item>
      <title>Spotify Cleaner</title>
      <dc:creator>Kelvin Wangonya</dc:creator>
      <pubDate>Wed, 25 Sep 2024 05:54:24 +0000</pubDate>
      <link>https://forem.com/wangonya/spotify-cleaner-1o91</link>
      <guid>https://forem.com/wangonya/spotify-cleaner-1o91</guid>
      <description>&lt;p&gt;I've had my Spotify account for a long time, and over that time, my musical tastes have evolved. As a consequence, there are a lot of liked songs, playlists, and podcasts I no longer listen to but have in my library. There's nothing wrong with keeping them in the library—I don't have them downloaded, so they're not really taking up any space—but I don't like the clutter.&lt;/p&gt;

&lt;p&gt;As far as I know, Spotify doesn't provide any way to batch delete things, so I created a &lt;a href="https://github.com/wangonya/spotify-cleaner" rel="noopener noreferrer"&gt;spotify-cleaner&lt;/a&gt; for this.&lt;/p&gt;

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

&lt;p&gt;I used it to just delete everything (all liked songs, albums, podcasts, and playlists), but it can also delete just a few selected items.&lt;/p&gt;

&lt;p&gt;If this is something you feel might also be useful to you, check the &lt;a href="https://github.com/wangonya/spotify-cleaner" rel="noopener noreferrer"&gt;repo&lt;/a&gt; for instructions on how to use it.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>python</category>
    </item>
    <item>
      <title>On embracing asynchronous communication</title>
      <dc:creator>Kelvin Wangonya</dc:creator>
      <pubDate>Mon, 01 Apr 2024 02:54:56 +0000</pubDate>
      <link>https://forem.com/wangonya/on-embracing-asynchronous-communication-25kn</link>
      <guid>https://forem.com/wangonya/on-embracing-asynchronous-communication-25kn</guid>
      <description>&lt;p&gt;I've been trying to improve my workflows this year. One thing I've known for a while, but never really got&lt;br&gt;
serious about implementing, is defaulting to asynchronous communication. I first learned about this concept&lt;br&gt;
a few years ago when reading &lt;a href="https://basecamp.com/handbook/how-we-work#asynchronously"&gt;Basecamp's Employee Handbook&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I work remotely for a company in the UK, and we use Slack for communication. The timezone difference is only&lt;br&gt;
two/three hours, so the whole team is usually all online at the same time for most of the day. I, (and I'm sure most other&lt;br&gt;
members of the team), just find it a lot easier to pull someone in for a 'quick' huddle each time there's something&lt;br&gt;
to discuss. I always hesitate before doing this, but end up doing it anyway most of the time. The fact that it is&lt;br&gt;
very easy to do doesn't help. I think it does more harm than good.&lt;/p&gt;

&lt;p&gt;More than a few times I try to invite someone for a huddle, and they're not available. I then have to write down&lt;br&gt;
whatever I wanted us to discuss - and it always ends up feeling more productive. I think better when I write. Or,&lt;br&gt;
maybe writing actually forces me to think about the thing more - something I may have been avoiding and choosing&lt;br&gt;
the easier option of 'discussing' it instead. I even come up with the solution sometimes in the middle of writing,&lt;br&gt;
and find the call, or even the message itself, not to be necessary.&lt;/p&gt;

&lt;p&gt;Another benefit of writing things down is that you can actually refer back to the messages months/years down the&lt;br&gt;
line when you can't figure out why a certain decision was made. I know Slack huddles have threads attached to&lt;br&gt;
them but we rarely ever use them. Handovers are a lot easier when you can actually search for something in threads&lt;br&gt;
and follow discussions that were made in the past.&lt;/p&gt;

&lt;p&gt;Writing is also great practice for communication skills as things usually need to be a lot more clear written down than spoken.&lt;/p&gt;

&lt;p&gt;I know these kinds of impromptu calls are distracting, but I understand why people do them. It's just the&lt;br&gt;
easiest thing to do in the moment. They're hardly ever necessary though.&lt;/p&gt;

&lt;p&gt;In my experience, I have never been in a situation where I would be completely blocked unless I got into a call&lt;br&gt;
to discuss something. I'm never in need of an immediate answer to be able to continue with my work. And even if&lt;br&gt;
I am, there's no guarantee that calling someone is going to get me that answer. They may not be available, and then&lt;br&gt;
I'll still have to write down whatever I wanted anyway.&lt;/p&gt;

&lt;p&gt;It's usually not worth breaking someone's focus over whatever issue I feel needs immediate attention. It's not easy,&lt;br&gt;
but it's worth embracing asynchronous communication more.&lt;/p&gt;

&lt;p&gt;Write something down as clearly and precisely as you can and send it.&lt;br&gt;
Don't expect an immediate reply. Find something else to do in the meantime.&lt;/p&gt;

&lt;p&gt;I think that's a better way to work.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>career</category>
    </item>
    <item>
      <title>Pydantic validators don't raise validation errors immediately</title>
      <dc:creator>Kelvin Wangonya</dc:creator>
      <pubDate>Wed, 06 Mar 2024 03:59:24 +0000</pubDate>
      <link>https://forem.com/wangonya/pydantic-validators-dont-raise-validation-errors-immediately-3p09</link>
      <guid>https://forem.com/wangonya/pydantic-validators-dont-raise-validation-errors-immediately-3p09</guid>
      <description>&lt;p&gt;This one really had me confused. I was facing an error with &lt;a href="https://docs.pydantic.dev/latest/concepts/validators/"&gt;Pydantic validators&lt;/a&gt; which I thought I had handled, but it just didn't work as expected.&lt;/p&gt;

&lt;p&gt;The schema looked 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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;dates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&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;start_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&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;end_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&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="bp"&gt;...&lt;/span&gt;

    &lt;span class="nd"&gt;@validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dates&lt;/span&gt;&lt;span class="sh"&gt;"&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_date_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;...&lt;/span&gt;

    &lt;span class="nd"&gt;@validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start_date&lt;/span&gt;&lt;span class="sh"&gt;"&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;set_start_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&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="c1"&gt;# some logic here that expects `dates` to exist
&lt;/span&gt;        &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&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;dates&lt;/span&gt;&lt;span class="sh"&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;

    &lt;span class="nd"&gt;@validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end_date&lt;/span&gt;&lt;span class="sh"&gt;"&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;set_end_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&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="c1"&gt;# some logic here that expects `dates` to exist
&lt;/span&gt;        &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&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;dates&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="mi"&gt;1&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;v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My assumption in the last two validators was that &lt;code&gt;dates&lt;/code&gt; would always be available because it's a required field.&lt;br&gt;
To my surprise, an &lt;code&gt;IndexError&lt;/code&gt; was always raised on &lt;code&gt;set_start_date&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So I thought, ok, I'll raise a &lt;code&gt;ValueError&lt;/code&gt; myself on &lt;code&gt;update_date_format&lt;/code&gt; since the required field check doesn't seem to be working.&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;Schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;dates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&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;start_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&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;end_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&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="bp"&gt;...&lt;/span&gt;

    &lt;span class="nd"&gt;@validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dates&lt;/span&gt;&lt;span class="sh"&gt;"&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_date_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dates is a required field&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;...&lt;/span&gt;

    &lt;span class="nd"&gt;@validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start_date&lt;/span&gt;&lt;span class="sh"&gt;"&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;set_start_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&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="c1"&gt;# some logic here that expects `dates` to exist
&lt;/span&gt;        &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&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;dates&lt;/span&gt;&lt;span class="sh"&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;

    &lt;span class="nd"&gt;@validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end_date&lt;/span&gt;&lt;span class="sh"&gt;"&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;set_end_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&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="c1"&gt;# some logic here that expects `dates` to exist
&lt;/span&gt;        &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&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;dates&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="mi"&gt;1&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;v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, an &lt;code&gt;IndexError&lt;/code&gt; was raised on &lt;code&gt;set_start_date&lt;/code&gt;. It's like my check on &lt;code&gt;update_date_format&lt;/code&gt; was completely ignored.&lt;br&gt;
I even put a breakpoint on the line to make sure it was being hit. It was.&lt;/p&gt;

&lt;p&gt;And then it hit me. Validation errrors aren't raised immediately. They're collected and returned all at once in the response.&lt;/p&gt;

&lt;p&gt;Despite seeing this countless times in &lt;code&gt;422&lt;/code&gt; responses, I hadn't ever thought about it. It made perfect sense.&lt;br&gt;
You don't want to return one error at a time as an API response. It's much better to return a response detailing everything that's wrong with the payload.&lt;/p&gt;

&lt;p&gt;From the &lt;a href="https://docs.pydantic.dev/latest/errors/errors/"&gt;docs&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One exception will be raised regardless of the number of errors found, that &lt;code&gt;ValidationError&lt;/code&gt; will contain information about all the errors and how they happened.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The important thing to remember is that this only happens for validation errors (i.e errors raised through &lt;code&gt;ValueError&lt;/code&gt; or &lt;code&gt;AssertionError&lt;/code&gt;).&lt;br&gt;
This is why the &lt;code&gt;IndexError&lt;/code&gt; was always raised immediately it happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to stop validation on the first error
&lt;/h2&gt;

&lt;p&gt;According to &lt;a href="https://stackoverflow.com/a/69538066/9312256"&gt;this answer&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you have checks, the failure of which should interrupt the further validation, then put them in the &lt;code&gt;pre=True&lt;/code&gt; root validator. Because field validation will not occur if &lt;code&gt;pre=True&lt;/code&gt; root validators raise an error.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;


&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PayloadValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="n"&gt;emailId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&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;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

   &lt;span class="nd"&gt;@root_validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pre&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;root_validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&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;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&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;emailId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
           &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Email list is empty.&lt;/span&gt;&lt;span class="sh"&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;values&lt;/span&gt;

   &lt;span class="nd"&gt;@validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;emailId&lt;/span&gt;&lt;span class="sh"&gt;"&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;valid_domains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;emailId&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;emailId&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

</description>
      <category>python</category>
      <category>pydantic</category>
      <category>todayilearned</category>
    </item>
    <item>
      <title>Taking notes</title>
      <dc:creator>Kelvin Wangonya</dc:creator>
      <pubDate>Sat, 24 Jun 2023 21:08:48 +0000</pubDate>
      <link>https://forem.com/wangonya/taking-notes-2ie0</link>
      <guid>https://forem.com/wangonya/taking-notes-2ie0</guid>
      <description>&lt;p&gt;I need to get in the habit of taking notes while I work. Especially when I'm debugging. I've noticed a poor habit reoccur when I'm working on a particularly difficult problem.&lt;/p&gt;

&lt;p&gt;It usually goes something like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start working on problem. Do some research. Try out &lt;code&gt;A&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;A&lt;/code&gt; doesn't work. Do some more research. Try &lt;code&gt;B&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;B&lt;/code&gt; doesn't work.&lt;/li&gt;
&lt;li&gt;Try &lt;code&gt;C&lt;/code&gt;. Nothing.&lt;/li&gt;
&lt;li&gt;At this point I'm already tired so I think to myself, "&lt;code&gt;A&lt;/code&gt; seems like a pretty good idea. Why didn't it work? Let's try it again.".&lt;/li&gt;
&lt;li&gt;Oh, that's why &lt;code&gt;A&lt;/code&gt; didn't work. Of course.&lt;/li&gt;
&lt;li&gt;Try some variation of &lt;code&gt;B&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Try &lt;code&gt;C&lt;/code&gt; again.&lt;/li&gt;
&lt;li&gt;Think about &lt;code&gt;A&lt;/code&gt; some more...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So much time is wasted, with very little progress made. Even worse, if this is at the end of the day and I have to leave it pending and come back to it the next day - or if I have to stash the changes and work on something else for a while then come back to it later, then the cycle begins all over again.&lt;/p&gt;

&lt;p&gt;If notes are taken at each step of the way, it's clear what works and what doesn't - and why. This isn't just helpful in the moment. Chances are I'll come across a similar issue in the future. I can't always rely on memory on these things. Experience has shown it to be very unreliable.&lt;/p&gt;

&lt;p&gt;As much as I plan to mostly use pen and paper for this, it should also give me ample opportunity to blog more. Many problems should be general enough for someone&lt;sup id="fnref1"&gt;1&lt;/sup&gt; else out there to benefit from.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Barely anyone reads my blog, I know, but it should at least act as a backup to my pen and paper. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>learning</category>
    </item>
    <item>
      <title>Handling missing dict keys</title>
      <dc:creator>Kelvin Wangonya</dc:creator>
      <pubDate>Thu, 10 Feb 2022 15:25:42 +0000</pubDate>
      <link>https://forem.com/wangonya/handling-missing-dict-keys-2djo</link>
      <guid>https://forem.com/wangonya/handling-missing-dict-keys-2djo</guid>
      <description>&lt;p&gt;Trying to access a non-existent key using this notation raises a &lt;code&gt;KeyError&lt;/code&gt;: &lt;code&gt;dict[key]&lt;/code&gt;. An easy workaround for this is to use &lt;code&gt;get(key)&lt;/code&gt; instead, which returns &lt;code&gt;None&lt;/code&gt; if &lt;code&gt;key&lt;/code&gt; isn't found, or &lt;code&gt;get(key, default)&lt;/code&gt; which returns &lt;code&gt;default&lt;/code&gt; if &lt;code&gt;key&lt;/code&gt; isn't found.&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="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; d &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; d
&lt;span class="o"&gt;{}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; d[&lt;span class="s2"&gt;"x"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
Traceback &lt;span class="o"&gt;(&lt;/span&gt;most recent call last&lt;span class="o"&gt;)&lt;/span&gt;:
  File &lt;span class="s2"&gt;"&amp;lt;stdin&amp;gt;"&lt;/span&gt;, line 1, &lt;span class="k"&gt;in&lt;/span&gt; &amp;lt;module&amp;gt;
KeyError: &lt;span class="s1"&gt;'x'&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; d.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"x"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; d
&lt;span class="o"&gt;{}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; d.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"x"&lt;/span&gt;,&lt;span class="s2"&gt;"y"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;'y'&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; d
&lt;span class="o"&gt;{}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But what if you want to not only return a default if the key is missing, but also assign that default to the specified key in the dict? There are a couple of ways to do that.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. &lt;code&gt;setdefault&lt;/code&gt;
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;h3&gt;
  
  
  setdefault(key[, default])
&lt;/h3&gt;

&lt;p&gt;If key is in the dictionary, return its value. If not, insert key with a value of default and return default. &lt;code&gt;default&lt;/code&gt; defaults to &lt;code&gt;None&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.python.org/3/library/stdtypes.html#dict.setdefault"&gt;docs&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; d
&lt;span class="o"&gt;{}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; d.setdefault&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"x"&lt;/span&gt;,&lt;span class="s2"&gt;"y"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;'y'&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; d
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'x'&lt;/span&gt;: &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. &lt;code&gt;defaultdict&lt;/code&gt;
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;h3&gt;
  
  
  class collections.defaultdict(default_factory=None, /[, ...])
&lt;/h3&gt;

&lt;p&gt;The first argument provides the initial value for the &lt;a href="https://docs.python.org/3/library/collections.html#collections.defaultdict.default_factory"&gt;default_factory&lt;/a&gt; attribute; it defaults to &lt;code&gt;None&lt;/code&gt;. All remaining arguments are treated the same as if they were passed to the &lt;a href="https://docs.python.org/3/library/stdtypes.html#dict"&gt;dict&lt;/a&gt; constructor, including keyword arguments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.python.org/3/library/collections.html#collections.defaultdict"&gt;docs&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; from collections import defaultdict
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; i &lt;span class="o"&gt;=&lt;/span&gt; defaultdict&lt;span class="o"&gt;(&lt;/span&gt;int&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; i
defaultdict&lt;span class="o"&gt;(&lt;/span&gt;&amp;lt;class &lt;span class="s1"&gt;'int'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &lt;span class="o"&gt;{})&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; i[&lt;span class="s2"&gt;"x"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
0
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; i
defaultdict&lt;span class="o"&gt;(&lt;/span&gt;&amp;lt;class &lt;span class="s1"&gt;'int'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'x'&lt;/span&gt;: 0&lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; i[&lt;span class="s2"&gt;"y"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
0
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; i
defaultdict&lt;span class="o"&gt;(&lt;/span&gt;&amp;lt;class &lt;span class="s1"&gt;'int'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'x'&lt;/span&gt;: 0, &lt;span class="s1"&gt;'y'&lt;/span&gt;: 0&lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that no &lt;code&gt;KeyError&lt;/code&gt; is raised despite the key not existing at first. Instead, the key is created with the default value of the type passed into &lt;code&gt;defaultdict&lt;/code&gt;, in this case &lt;code&gt;0&lt;/code&gt; for &lt;code&gt;int&lt;/code&gt;. If we used &lt;code&gt;list&lt;/code&gt; instead, the default value would be &lt;code&gt;[]&lt;/code&gt;, an so on.&lt;/p&gt;

&lt;p&gt;Further reading:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/library/collections.html#defaultdict-examples"&gt;defaultdict Examples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://stackoverflow.com/questions/3483520/use-cases-for-the-setdefault-dict-method"&gt;Use cases for the 'setdefault' dict method&lt;/a&gt; (vs defaultdict)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Implement &lt;code&gt;__missing__&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This is actually what &lt;code&gt;defaultdict&lt;/code&gt; does behind the scenes. Use this when &lt;code&gt;defaultdict&lt;/code&gt; doesn't fit your usecase.&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="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; class M&lt;span class="o"&gt;(&lt;/span&gt;dict&lt;span class="o"&gt;)&lt;/span&gt;:
...     def __missing__&lt;span class="o"&gt;(&lt;/span&gt;self, key&lt;span class="o"&gt;)&lt;/span&gt;:
...             value &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my default value"&lt;/span&gt;
...             self[key] &lt;span class="o"&gt;=&lt;/span&gt; value
...             &lt;span class="k"&gt;return &lt;/span&gt;value
...
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; m &lt;span class="o"&gt;=&lt;/span&gt; M&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; m
&lt;span class="o"&gt;{}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; m[&lt;span class="s2"&gt;"x"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="s1"&gt;'my default value'&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; m
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'x'&lt;/span&gt;: &lt;span class="s1"&gt;'my default value'&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; m[&lt;span class="s2"&gt;"y"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="s1"&gt;'my default value'&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; m
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'x'&lt;/span&gt;: &lt;span class="s1"&gt;'my default value'&lt;/span&gt;, &lt;span class="s1"&gt;'y'&lt;/span&gt;: &lt;span class="s1"&gt;'my default value'&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>beginners</category>
      <category>python</category>
    </item>
    <item>
      <title>You can use your database as a simple calculator</title>
      <dc:creator>Kelvin Wangonya</dc:creator>
      <pubDate>Sat, 23 Oct 2021 15:29:20 +0000</pubDate>
      <link>https://forem.com/wangonya/you-can-use-your-database-as-a-simple-calculator-3fdi</link>
      <guid>https://forem.com/wangonya/you-can-use-your-database-as-a-simple-calculator-3fdi</guid>
      <description>&lt;p&gt;Saw this in the &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/entering-queries.html"&gt;MySQL docs&lt;/a&gt;. I'm assuming this only works for relational databases.&lt;/p&gt;

&lt;p&gt;I don't know if it's very useful but definitely interesting.&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="n"&gt;MariaDB&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;SIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&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="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;------------------+---------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;SIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;4&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;4&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="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;------------------+---------+&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="mi"&gt;70710678118655&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;      &lt;span class="mi"&gt;25&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;------------------+---------+&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;set&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="mi"&gt;02&lt;/span&gt; &lt;span class="n"&gt;sec&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 sql"&gt;&lt;code&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;=#&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;SIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&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="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;sin&lt;/span&gt;         &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="k"&gt;column&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
&lt;span class="c1"&gt;--------------------+----------&lt;/span&gt;
 &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;7071067811865475&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;       &lt;span class="mi"&gt;25&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>database</category>
      <category>todayilearned</category>
    </item>
    <item>
      <title>Getting the running Go version</title>
      <dc:creator>Kelvin Wangonya</dc:creator>
      <pubDate>Sat, 24 Jul 2021 03:01:19 +0000</pubDate>
      <link>https://forem.com/wangonya/getting-the-running-go-version-3gjp</link>
      <guid>https://forem.com/wangonya/getting-the-running-go-version-3gjp</guid>
      <description>&lt;p&gt;This would mostly be useful for debugging.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"runtime"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&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;"Running Go: %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&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;Output: &lt;code&gt;Running Go: go1.16.6&lt;/code&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Writing DRYer tests using Pytest parametrize</title>
      <dc:creator>Kelvin Wangonya</dc:creator>
      <pubDate>Thu, 08 Apr 2021 16:29:08 +0000</pubDate>
      <link>https://forem.com/wangonya/writing-dryer-tests-using-pytest-parametrize-5e7l</link>
      <guid>https://forem.com/wangonya/writing-dryer-tests-using-pytest-parametrize-5e7l</guid>
      <description>&lt;p&gt;Tests tend to not always be so DRY, which isn't necessarily a bad thing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stackoverflow.com/a/129722/9312256"&gt;This&lt;/a&gt; SO answer sums it up nicely:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Readability is more important for tests. If a test fails, you want the problem to be obvious. The developer shouldn't have to wade through a lot of heavily factored test code to determine exactly what failed. You don't want your test code to become so complex that you need to write unit-test-tests.&lt;/p&gt;

&lt;p&gt;However, eliminating duplication is usually a good thing, as long as it doesn't obscure anything, and eliminating the duplication in your tests may lead to a better API. Just make sure you don't go past the point of diminishing returns.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pytest gives some ways to reduce duplication with fixtures.&lt;/p&gt;

&lt;p&gt;Say you had a couple of endpoints that return data to be used in a report.&lt;br&gt;
The report data is supposed to be displayed in an excel sheet with different sheets.&lt;/p&gt;

&lt;p&gt;Sometimes, only data for one sheet is required. Other times, data for all the sheets is fetched. So the endpoints end up being broken down 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;/report/sheet-a
/report/sheet-b
/report/sheet-c
/report/sheet-d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's take a simple test case: checking that the endpoints return &lt;code&gt;200&lt;/code&gt; when called.&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="nn"&gt;pytest&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_report_sheet_a_returns_200&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/report/sheet-a'&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_report_sheet_b_returns_200&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/report/sheet-b'&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_report_sheet_c_returns_200&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/report/sheet-c'&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_report_sheet_d_returns_200&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/report/sheet-d'&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This might not look too bad, but if we wanted to test for another operation on the endpoints - checking if the endpoints require authentication for example - you get the feeling that this can be done a little bit better.&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="nn"&gt;pytest&lt;/span&gt;

&lt;span class="c1"&gt;# test GET requests
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_report_sheet_a_returns_200&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/report/sheet-a'&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_report_sheet_b_returns_200&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/report/sheet-b'&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_report_sheet_c_returns_200&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/report/sheet-c'&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_report_sheet_d_returns_200&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/report/sheet-d'&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

&lt;span class="c1"&gt;# test auth
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_report_sheet_a_requires_auth&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;unauthorized_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/report/sheet-a'&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_report_sheet_b_requires_auth&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;unauthorized_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/report/sheet-b'&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_report_sheet_c_requires_auth&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;unauthorized_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/report/sheet-c'&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_report_sheet_d_requires_auth&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;unauthorized_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/report/sheet-d'&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the only thing changing in the tests is the endpoints. Everything else remains the same.&lt;/p&gt;

&lt;p&gt;Given that all the sheets belong to one report, we can refactor the tests to reduce duplication without sacrificing readability.&lt;/p&gt;

&lt;p&gt;Here's how the tests can be rewritten using &lt;a href="https://docs.pytest.org/en/stable/parametrize.html#parametrize-basics"&gt;Pytest parametrize&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="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pytest&lt;/span&gt;

&lt;span class="n"&gt;report_sheet_endpoints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;'/sheet-a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;'/sheet-b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;'/sheet-c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;'/sheet-d'&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;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parametrize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'endpoint'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;report_sheet_endpoints&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_report_sheets_return_200&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;'/report&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;


&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parametrize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'endpoint'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;report_sheet_endpoints&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_report_sheets_require_auth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;unauthorized_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;'/report&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have two tests instead of eight. But when you run the tests, 8 tests will run, not 2. Pytest takes each value in &lt;code&gt;report_sheet_endpoints&lt;/code&gt; and feeds it into the test.&lt;br&gt;
This reduces duplication while maintaining readability.&lt;/p&gt;

&lt;p&gt;Running the tests gives this output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;test_reports.py::test_report_sheets_return_200[sheet-a] PASSED
test_reports.py::test_report_sheets_return_200[sheet-b] PASSED
test_reports.py::test_report_sheets_return_200[sheet-c] PASSED
test_reports.py::test_report_sheets_return_200[sheet-d] PASSED

test_reports.py::test_report_sheets_require_auth[sheet-a] PASSED
test_reports.py::test_report_sheets_require_auth[sheet-b] PASSED
test_reports.py::test_report_sheets_require_auth[sheet-c] PASSED
test_reports.py::test_report_sheets_require_auth[sheet-d] PASSED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>python</category>
      <category>pytest</category>
      <category>tdd</category>
      <category>todayilearned</category>
    </item>
    <item>
      <title>Hackathon submission</title>
      <dc:creator>Kelvin Wangonya</dc:creator>
      <pubDate>Sat, 09 Jan 2021 18:53:58 +0000</pubDate>
      <link>https://forem.com/wangonya/hackathon-submission-3jog</link>
      <guid>https://forem.com/wangonya/hackathon-submission-3jog</guid>
      <description>&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;An auto repair shop management system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Category Submission:
&lt;/h3&gt;

&lt;p&gt;Built for Business&lt;/p&gt;

&lt;h3&gt;
  
  
  App Link
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://auto-repair-saas-3exc8.ondigitalocean.app"&gt;Link&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

&lt;p&gt;Registration:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--N32WzW-t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jr8gwq9dn93azd9v87fb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--N32WzW-t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jr8gwq9dn93azd9v87fb.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Login:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NBXdng0D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ga8rpjwmbgzjvuc96w0r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NBXdng0D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ga8rpjwmbgzjvuc96w0r.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dashboard:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AMbG2bUv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/voqm7byz9tvppfx42i9f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AMbG2bUv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/voqm7byz9tvppfx42i9f.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Jobs:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rQTwvDwX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gxqb0iysvfvojkwfnjpe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rQTwvDwX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gxqb0iysvfvojkwfnjpe.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Contacts:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--E4uy0uF---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/iirb6vem3hm53ffk2642.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--E4uy0uF---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/iirb6vem3hm53ffk2642.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vehicles:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hvDn8Lye--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/msb2qlgq1z1i93gl3yhd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hvDn8Lye--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/msb2qlgq1z1i93gl3yhd.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Staff:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hINvGnkT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1n918hheudrvylfobdjd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hINvGnkT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1n918hheudrvylfobdjd.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Description
&lt;/h3&gt;

&lt;p&gt;This is a system designed to help auto repair shops manage and keep track of jobs, clients, vehicles, etc. You can read a full description on the project's &lt;a href="https://github.com/wangonya/auto-repair-saas/blob/main/README.md"&gt;Readme&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Link to Source Code
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/wangonya/auto-repair-saas"&gt;Link&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Permissive License
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/wangonya/auto-repair-saas/blob/main/LICENSE"&gt;Apache License 2.0&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;I have a friend who owns an auto repair shop. Process are being handled manually and as the shop grows this is getting out of hand. We had a conversation about coming up with a simple system to keep track of clients and jobs and this Hackathon gave me the perfect platform to get this done. &lt;/p&gt;

&lt;p&gt;I'll continue improving the system to a point where it's fully usable by him i.e reduce any manual labor on their side as much as possible. Later on, this can be expanded to a few other local shops.&lt;/p&gt;

&lt;h3&gt;
  
  
  How I built it
&lt;/h3&gt;

&lt;p&gt;I used Django + Postgres to build this. I followed the simple instructions on the &lt;a href="https://github.com/digitalocean/sample-django"&gt;sample django&lt;/a&gt; repo provided by Digital Ocean. I also added integration with Sendgrid for emails.&lt;/p&gt;

&lt;p&gt;I had never used Digital Ocean for anything before but I didn't face any issues with the deployment.&lt;/p&gt;

&lt;p&gt;I learned my way around the platform and I see myself using it for my other side projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Resources/Info
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;To get started quickly, you can generate dummy data on the jobs page. This will create jobs, contacts, vehicles and staff to work with.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Apparently, Safari browser doesn't show a dropdown calendar on HTML date fields natively like other browsers. I'm yet to fix this so if you'll be testing the system on Safari, the date fields on the job creation form will appear like regular text fields.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dohackathon</category>
    </item>
    <item>
      <title>Progress: Search</title>
      <dc:creator>Kelvin Wangonya</dc:creator>
      <pubDate>Tue, 29 Dec 2020 05:13:50 +0000</pubDate>
      <link>https://forem.com/wangonya/progress-search-38o</link>
      <guid>https://forem.com/wangonya/progress-search-38o</guid>
      <description>&lt;h2&gt;
  
  
  Search
&lt;/h2&gt;

&lt;p&gt;A simple implementation of Django's &lt;a href="https://docs.djangoproject.com/en/3.1/ref/contrib/postgres/search/"&gt;full text search&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NiHtx6Ku--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/d50khcn72rsag4djd0tw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NiHtx6Ku--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/d50khcn72rsag4djd0tw.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--C2XIDX1V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/tdy7bzrlque00bbwz7rv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--C2XIDX1V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/tdy7bzrlque00bbwz7rv.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kdrHRK1y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0fisnifxlvdl6m5q33pq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kdrHRK1y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0fisnifxlvdl6m5q33pq.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Various bug fixes and improvements before submission&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dohackathon</category>
    </item>
    <item>
      <title>Progress: Improved dashboard (with real data) and "no data" placeholders</title>
      <dc:creator>Kelvin Wangonya</dc:creator>
      <pubDate>Sun, 27 Dec 2020 12:32:49 +0000</pubDate>
      <link>https://forem.com/wangonya/progress-improved-dashboard-with-real-data-and-no-data-placeholders-4km5</link>
      <guid>https://forem.com/wangonya/progress-improved-dashboard-with-real-data-and-no-data-placeholders-4km5</guid>
      <description>&lt;h2&gt;
  
  
  Dashboard
&lt;/h2&gt;

&lt;p&gt;The dashboard now pulls data from the database based on the selected time period (week/month/year). I'm using Chartjs for the charts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9gnKwB0h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/e17vkdnwc7xkhcw0rqzv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9gnKwB0h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/e17vkdnwc7xkhcw0rqzv.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  "No data" placeholders
&lt;/h2&gt;

&lt;p&gt;Instead of showing a blank screen on login if there's no data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lfcn4Ngu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8siqfxennwuzwx24z32d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lfcn4Ngu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8siqfxennwuzwx24z32d.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Enable search for vehicles, contacts and staff&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dohackathon</category>
    </item>
  </channel>
</rss>
