<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Siddhant Goel</title>
    <subtitle>Staff Software Engineer with decade plus experience building and managing production software. Active Maintainer of multiple Python open-source libraries with millions of PyPI downloads</subtitle>
    <link rel="self" type="application/atom+xml" href="https://sgoel.dev/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://sgoel.dev"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-06-02T00:00:00+00:00</updated>
    <id>https://sgoel.dev/atom.xml</id>
    <entry xml:lang="en">
        <title>Self-hosting static sites on a Caddy-powered VPS</title>
        <published>2026-06-02T00:00:00+00:00</published>
        <updated>2026-06-02T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/self-hosting-static-sites-on-a-caddy-powered-vps/"/>
        <id>https://sgoel.dev/posts/self-hosting-static-sites-on-a-caddy-powered-vps/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/self-hosting-static-sites-on-a-caddy-powered-vps/">&lt;p&gt;I operate a few different static websites, relatively low-traffic ones. So far these
sites were hosted on managed hosting providers, including Netlify, Cloudflare Pages, and
Bunny CDN. Last weekend I moved most of them over to an Ubuntu Linux VPS, where they&#x27;re
being served using &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;caddyserver.com&quot;&gt;Caddy&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;In this post I&#x27;ll walk through why I made the switch, how the new setup works, what I
like about it, and how Caddy makes hosting multiple domains from one server surprisingly
painless.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-move-away-from-managed-hosting&quot;&gt;Why move away from managed hosting&lt;&#x2F;h2&gt;
&lt;p&gt;Managed hosting is a fine way to serve websites, especially if the contents are static.
For the longest time, Netlify was my go-to option for serving static sites. Over time
though, I started getting bothered by the fact that I was adapting more and more of my
workflows according to how the provider worked instead of what I actually needed.&lt;&#x2F;p&gt;
&lt;p&gt;This was visible in mainly two aspects - configuration and deployment workflows.&lt;&#x2F;p&gt;
&lt;p&gt;Every hosting provider has their own way of setting configuration. If you need to set a
specific HTTP header, for instance, you either define it in their user interface
somewhere or put it in a configuration file. Netlify, for instance, has a &lt;code&gt;netlify.toml&lt;&#x2F;code&gt;
where all this stuff can go. Maybe it&#x27;s just because I&#x27;m older now, but over time I&#x27;ve
come to the realization that I want &lt;a href=&quot;https:&#x2F;&#x2F;sgoel.dev&#x2F;posts&#x2F;trying-out-zed-after-more-than-a-decade-of-vim-neovim&#x2F;&quot;&gt;less configuration&lt;&#x2F;a&gt; in my life, not more. I
want things to just work, and the less custom configuration there is the better.&lt;&#x2F;p&gt;
&lt;p&gt;Deployment workflows were a similar story. When setting up automatic deployments I need
to allow access to the Github&#x2F;Gitlab repository. One of my static sites was hosted on
Bunny CDN backed by a pull zone and a storage bucket. Since Bunny doesn&#x27;t provide a
native way to deploy static sites, I had Claude vibe-code a Python script that would
copy files from the local disk to the bucket and then manually purge the cache. It
worked fine, but it was yet another thing for me to maintain.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;setting-up-the-vps&quot;&gt;Setting up the VPS&lt;&#x2F;h2&gt;
&lt;p&gt;In my mind, the only logical solution was to set up a Linux VPS and put those static
sites behind a proxy server. I&#x27;ve operated Linux servers in the past, and self-hosting
static websites is really not a lot of work. It&#x27;ll still involve configuring bits and
pieces, but at least the configuration would not be tied to a hosting provider.&lt;&#x2F;p&gt;
&lt;p&gt;So I looked around for cheap VPS providers, ideally based in the EU, and came across
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.netcup.com&#x2F;en&quot;&gt;Netcup&lt;&#x2F;a&gt;. They offered a plan costing €3 per month for a server with 2 CPU cores, 2GB
RAM, and 60GB SSD, which sounded more than enough for what I needed, so I provisioned a
VPS on that plan and got to work.&lt;&#x2F;p&gt;
&lt;p&gt;The first step was to generate an SSH key and copy it over to the VPS for password-less
login. Next, I set up a few infrastructure definitions using &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pyinfra.com&#x2F;&quot;&gt;pyinfra&lt;&#x2F;a&gt; so that all the
changes I make to the server are contained in source files somewhere.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;setting-up-pyinfra-inventory&quot;&gt;Setting up pyinfra inventory&lt;&#x2F;h2&gt;
&lt;p&gt;When using pyinfra, the host configuration is usually put in something called an
&quot;inventory&quot;, that defines the IP address of the VPS along with the SSH configuration
needed to log in to it.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# inventory.py&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; os.path&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;servers&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;quot;1.2.3.4&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;            &amp;quot;ssh_user&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;username&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;            &amp;quot;ssh_key&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: os.path.expanduser(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;~&#x2F;.ssh&#x2F;id_rsa&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;            &amp;quot;_sudo&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; True&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It&#x27;s basically just a Python list of servers where each entry is a tuple containing the
server&#x27;s IP address and SSH arguments. With this inventory in place, pyinfra is able to
log in to the VPS on our behalf to perform actions.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;setting-up-pyinfra-operations&quot;&gt;Setting up pyinfra Operations&lt;&#x2F;h2&gt;
&lt;p&gt;The next step after configuring a pyinfra inventory is to define a few operations.
Operations are how we can instruct pyinfra to take actions and make changes on the
target machines.&lt;&#x2F;p&gt;
&lt;p&gt;Below is the complete &lt;code&gt;deploy.py&lt;&#x2F;code&gt; with all operations. Each section is explained
afterward.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# deploy.py&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; pyinfra.operations&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; apt, files, server, systemd&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;apt.packages(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;    name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;Ensure base packages&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; packages&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;caddy&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;rsync&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;],&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; update&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;True&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;server.shell(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;    name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;Set up ufw&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;    commands&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;quot;ufw default deny incoming&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;quot;ufw default allow outgoing&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;quot;ufw allow 22&#x2F;tcp comment &amp;#39;SSH&amp;#39;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;quot;ufw allow 80&#x2F;tcp comment &amp;#39;HTTP&amp;#39;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;quot;ufw allow 443&#x2F;tcp comment &amp;#39;HTTPS&amp;#39;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;quot;ufw enable&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;files.directory(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;    path&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&#x2F;srv&#x2F;example.com&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;    present&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;True&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;    recursive&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;True&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;    user&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;caddy&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;    group&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;caddy&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;caddyfile&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; files.template(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;    name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;Update Caddy configuration&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;    src&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;Caddyfile&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;    dest&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&#x2F;etc&#x2F;caddy&#x2F;Caddyfile&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;systemd.service(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;    name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;Start and enable Caddy service&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;    service&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;caddy.service&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;    running&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;True&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;    enabled&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;True&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;    restarted&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;caddyfile.will_change,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;    daemon_reload&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;caddyfile.will_change,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Here&#x27;s what each section does.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;setting-up-a-firewall&quot;&gt;Setting up a firewall&lt;&#x2F;h3&gt;
&lt;p&gt;The &lt;code&gt;server.shell&lt;&#x2F;code&gt; block configures &lt;code&gt;ufw&lt;&#x2F;code&gt; to deny all incoming traffic by default and
allow outgoing traffic, then opens ports 22 (SSH), 80 (HTTP), and 443 (HTTPS).&lt;&#x2F;p&gt;
&lt;p&gt;pyinfra currently does not have a native operation for &lt;code&gt;ufw&lt;&#x2F;code&gt;, the rationale being that
&lt;code&gt;ufw&lt;&#x2F;code&gt; rules ultimately translate to &lt;code&gt;operations.iptables&lt;&#x2F;code&gt; rules. I tried using
&lt;code&gt;iptables&lt;&#x2F;code&gt; but ran into a bunch of errors, so I decided to just call &lt;code&gt;ufw&lt;&#x2F;code&gt; via
&lt;code&gt;operations.server&lt;&#x2F;code&gt; and move on.&lt;&#x2F;p&gt;
&lt;p&gt;Beyond the firewall, it may be worth spending a bit more time on hardening SSH access.
Things like disabling password authentication and optionally changing the default port
would significantly reduce brute-force attempts.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;installing-caddy-and-hosting-multiple-domains&quot;&gt;Installing Caddy and hosting multiple domains&lt;&#x2F;h3&gt;
&lt;p&gt;After installing Caddy, it&#x27;s likely that Caddy&#x27;s default configuration is not what you
want since you probably have your own websites you&#x27;d like to deploy.&lt;&#x2F;p&gt;
&lt;p&gt;Serving static sites is quite straightforward using Caddy. Assuming that the contents of
your static website (HTML, CSS, JS, etc.) is at &lt;code&gt;&#x2F;srv&#x2F;example.com&lt;&#x2F;code&gt;, here is a minimal
(but fully functional) Caddy configuration:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;example.com {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    root * &#x2F;srv&#x2F;example.com&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    encode gzip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    file_server&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    log {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        format console&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        output file &#x2F;var&#x2F;log&#x2F;caddy&#x2F;example.com.log {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            roll_size 10MB&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;www.example.com {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    redir https:&#x2F;&#x2F;example.com{uri} permanent&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Copy the contents of the snippet above into a local file (let&#x27;s name it &lt;code&gt;Caddyfile&lt;&#x2F;code&gt;)
next to &lt;code&gt;inventory.py&lt;&#x2F;code&gt; and &lt;code&gt;deploy.py&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Looking back at the &lt;code&gt;deploy.py&lt;&#x2F;code&gt; script, the &lt;code&gt;apt.packages&lt;&#x2F;code&gt; call installs Caddy and
rsync (we&#x27;ll need rsync later for deployments). The &lt;code&gt;files.directory&lt;&#x2F;code&gt; call creates
&lt;code&gt;&#x2F;srv&#x2F;example.com&lt;&#x2F;code&gt; and makes
sure that this directory is owned by the &lt;code&gt;caddy&lt;&#x2F;code&gt; user and group. This is where the
contents of our static site would go, and the correct user&#x2F;group permissions are
important for Caddy to be able to read (and serve) files from this directory.&lt;&#x2F;p&gt;
&lt;p&gt;Next, &lt;code&gt;files.template&lt;&#x2F;code&gt; copies the Caddyfile from local disk to
&lt;code&gt;&#x2F;etc&#x2F;caddy&#x2F;Caddyfile&lt;&#x2F;code&gt;, and &lt;code&gt;systemd.service&lt;&#x2F;code&gt; runs and enables the Caddy service,
also making sure that whenever the contents of &lt;code&gt;Caddyfile&lt;&#x2F;code&gt; change, the associated
systemd service and the systemd daemon itself is restarted.&lt;&#x2F;p&gt;
&lt;p&gt;One thing to keep in mind: make sure your domain&#x27;s DNS records (an A record for the apex
and a CNAME for &lt;code&gt;www&lt;&#x2F;code&gt;) point to the VPS IP address before starting Caddy. Caddy needs
the domain to resolve before it can provision TLS certificates automatically.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;deployment-workflow&quot;&gt;Deployment Workflow&lt;&#x2F;h2&gt;
&lt;p&gt;After all this is in place, the last remaining piece is how to deploy your website. The
contents of the site will be deployed to &lt;code&gt;&#x2F;srv&#x2F;example.com&lt;&#x2F;code&gt; on the VPS, so the
&quot;deployment&quot; process is nothing more than copying a bunch of files from the local disk
to the VPS, which is exactly where &lt;code&gt;rsync&lt;&#x2F;code&gt; shines!&lt;&#x2F;p&gt;
&lt;p&gt;There are a few different ways to use &lt;code&gt;rsync&lt;&#x2F;code&gt; to copy contents over. I&#x27;ve personally
defined a &lt;code&gt;deploy-site&lt;&#x2F;code&gt; bash script that looks as follows:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;#!&#x2F;usr&#x2F;bin&#x2F;env bash&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;set -e&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; $#&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; -ne&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span&gt; ];&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    echo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Usage: $(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;basename&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;$0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;) &amp;lt;source_dir&amp;gt; &amp;lt;domain&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;&amp;amp;2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    exit 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ! -d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;$1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; ];&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    echo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Error: source directory &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;$1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39; does not exist&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;&amp;amp;2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    exit 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;fi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;rsync&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -avz --progress --rsync-path&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;sudo -u caddy rsync&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;${1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;%&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&#x2F;&amp;quot; &amp;quot;vps:&#x2F;srv&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;$2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;and placed it at &lt;code&gt;$HOME&#x2F;.local&#x2F;bin&#x2F;deploy-site&lt;&#x2F;code&gt;. Whenever I start a new project,
independent of how the final HTML&#x2F;CSS&#x2F;... artifacts are generated, I call this script
like &lt;code&gt;deploy-site dist&#x2F; example.com&lt;&#x2F;code&gt;, that calls rsync, and within just a few seconds
the site is deployed.&lt;&#x2F;p&gt;
&lt;p&gt;Note that the &lt;code&gt;--rsync-path &quot;sudo -u caddy rsync&quot;&lt;&#x2F;code&gt; flag requires the remote user to have
passwordless sudo, which you&#x27;ll need to set up if you haven&#x27;t done so already.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;All in all, the migration took less than a day and the setup has been running without
issues since. The next thing I&#x27;d like to add is some form of monitoring, for instance
Prometheus metrics to keep track of CPU and memory usage, just for fun. Maybe that&#x27;s
what the next post will be about!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>What&#x27;s next for txtcv</title>
        <published>2026-05-10T00:00:00+00:00</published>
        <updated>2026-05-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/whats-next-for-txtcv/"/>
        <id>https://sgoel.dev/posts/whats-next-for-txtcv/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/whats-next-for-txtcv/">&lt;p&gt;A few months ago I &lt;a href=&quot;https:&#x2F;&#x2F;sgoel.dev&#x2F;posts&#x2F;building-a-cv-builder&#x2F;&quot;&gt;started&lt;&#x2F;a&gt; working on a CV builder for software developers. It has now
seen some usage beyond just me, and I&#x27;ve learned a few things over the course. This post
covers what I&#x27;m planning for txtcv in the near future.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;from-cli-powered-to-cli-first&quot;&gt;From CLI powered to CLI-first&lt;&#x2F;h2&gt;
&lt;p&gt;The first change I&#x27;d like to make is for the project to be more CLI-driven.&lt;&#x2F;p&gt;
&lt;p&gt;At the moment there &lt;em&gt;is&lt;&#x2F;em&gt; a CLI, but it&#x27;s more of a frontend rather than being the
application itself. If you sign up on txtcv.com, your CV and your job applications and
everything else is saved on the server. This choice made sense in the beginning, but the
more I work with it the more it bothers me. I&#x27;d like to give the user more control and
let them store all the data locally, on their disk. The most straightforward way of
doing that is to make the CLI front and center.&lt;&#x2F;p&gt;
&lt;p&gt;This doesn&#x27;t mean that I&#x27;d like to get rid of the backend. Running something on the
server lets me build features that aren&#x27;t possible just with a CLI (e.g. generating
cover letters using an LLM). Plus, I&#x27;d still like to be able to offer a paid tier, and
keeping track of license keys is a lot more ergonomic on the backend. Regardless, I
imagine this backend to provide optional features, while the majority of the application
runs on the user&#x27;s command line, in their control.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;open-source-core&quot;&gt;Open-source core&lt;&#x2F;h2&gt;
&lt;p&gt;Having a command-line app means the user also expects to be able to read the source
code. I personally feel a bit uneasy about installing binaries on my machine without
knowing what they do, so I imagine there are others that feel that way too.&lt;&#x2F;p&gt;
&lt;p&gt;So, I&#x27;d like for this CLI to be open-source. Given GitHub&#x27;s recent stability problems, I
decided to go with Tangled for this repository, and the source code will be accessible
at &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;tangled.org&#x2F;txtcv.com&#x2F;cli&quot;&gt;https:&#x2F;&#x2F;tangled.org&#x2F;txtcv.com&#x2F;cli&lt;&#x2F;a&gt;. Tangled, in case you don&#x27;t know, is a Git
hosting service that utilizes the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;atproto.com&quot;&gt;AT protocol&lt;&#x2F;a&gt; extensively.&lt;&#x2F;p&gt;
&lt;p&gt;The repository doesn&#x27;t contain much at the time of this writing, because it&#x27;s still
very early days for this change. But expect that to change over the next weeks!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;learning-go&quot;&gt;Learning Go&lt;&#x2F;h2&gt;
&lt;p&gt;Python has been my main programming language for more than a decade now. Recently, I&#x27;ve
been itching to experiment with another programming language. And rebuilding this
project, I believe, provides me with the perfect opportunity to learn something new.&lt;&#x2F;p&gt;
&lt;p&gt;Like many other software developers, I&#x27;ve been looking at Go and Rust recently. For this
project, I wanted to use a language that makes it easy to write command-line
applications and has a good deployment story. While both Go and Rust fit the bill, I
ultimately decided to pick Go because it&#x27;s a &lt;em&gt;lot&lt;&#x2F;em&gt; easier to learn as compared to Rust.&lt;&#x2F;p&gt;
&lt;p&gt;I spent some time trying to learn Rust, but it&#x27;s a massive language with a very steep
learning curve, which is a big barrier to get past before I can do anything useful with
it. In comparison, Go was much easier to get started with (I spent a weekend working
through the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;go.dev&#x2F;tour&#x2F;welcome&#x2F;1&quot;&gt;Tour of Go&lt;&#x2F;a&gt; and &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;gobyexample.com&quot;&gt;Go by Example&lt;&#x2F;a&gt;), and I was able to write useful code a lot
quicker. So, over the next few weeks I&#x27;d like to build out the application more and
more, and learn Go in the process.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;That&#x27;s the direction I&#x27;d like to take txtcv in: a CLI-first, open-source tool that gives
you full control over your data, with optional server-side features for the things that
benefit from it. And I get to learn a new language along the way, which is always fun.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>PostgreSQL on Kamal: Deployment, Configuration, and Backups</title>
        <published>2026-03-01T00:00:00+00:00</published>
        <updated>2026-03-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/postgresql-on-kamal-deployment-configuration-and-backups/"/>
        <id>https://sgoel.dev/posts/postgresql-on-kamal-deployment-configuration-and-backups/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/postgresql-on-kamal-deployment-configuration-and-backups/">&lt;p&gt;It&#x27;s been a few weeks since I started building &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;txtcv.com&quot;&gt;txtcv&lt;&#x2F;a&gt;, which is a simple career
management platform (CV builder, cover letter generator, job application tracker) aimed
mostly at software developers. Built using Django and some Htmx (where needed), it&#x27;s
deployed on a 2.50 €&#x2F;month machine on a VPS hosting provider using Kamal.&lt;&#x2F;p&gt;
&lt;p&gt;When starting out, I consciously made the decision to not use any cloud hosting
providers, mainly because I wanted a side-project to be &lt;strong&gt;fun&lt;&#x2F;strong&gt; to work on (and cloud
hosting providers take the fun out of deployments if you&#x27;re the kind of person who has
fun when deploying applications), but also because you can get a &lt;strong&gt;lot&lt;&#x2F;strong&gt; more processing
power on a VPS than on a cloud hosting provider for the same amount of money.&lt;&#x2F;p&gt;
&lt;p&gt;After looking around and experimenting with tools that would make this process easy, I
settled on Kamal.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;kamal-deploy&quot;&gt;Kamal Deploy&lt;&#x2F;h2&gt;
&lt;p&gt;Kamal, if you&#x27;re not familiar, is a command-line tool that lets you easily deploy web
applications to servers. Apart from deployments, it also handles tasks like setting up
backing services (e.g. database containers), automated SSL certificate provisioning and
renewal, zero-downtime deploys, etc.&lt;&#x2F;p&gt;
&lt;p&gt;The setup involves specifying a bunch of configuration in a YAML file – things like your
server&#x27;s IP address, where to fetch your application&#x27;s Docker image from, environment
variable definitions, etc. Once the setup is done, you can run &lt;code&gt;kamal deploy&lt;&#x2F;code&gt; and have
an updated version of your application running on your server within just a few minutes.
And if your application requires a backing service (such as a PostgreSQL database),
Kamal lets you define &quot;accessories&quot; in the configuration file that are ultimately
containers that run alongside your main web application, providing a specific service.
So, for instance, if you&#x27;re running a Django application that requires PostgreSQL, you
could define a &lt;code&gt;postgres&lt;&#x2F;code&gt; accessory that your Django application can connect to.&lt;&#x2F;p&gt;
&lt;p&gt;Once you have the workflow set up, it&#x27;s really quite painless.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;defining-a-postgresql-accessory&quot;&gt;Defining a PostgreSQL accessory&lt;&#x2F;h2&gt;
&lt;p&gt;The previous section handwaved accessories, but here&#x27;s an example YAML snippet that
hopefully makes the PostgreSQL setup in a Kamal context a bit more tangible.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;accessories&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;  postgres&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;    image&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; postgres:18&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;    host&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 1.2.3.4&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;    port&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;127.0.0.1:5432:5432&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;    env&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;      clear&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;        POSTGRES_DB&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; example&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;        POSTGRES_USER&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; example&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;      secret&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; POSTGRES_PASSWORD&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;    directories&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; data:&#x2F;var&#x2F;lib&#x2F;postgresql&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This snippet instructs Kamal to fetch the &lt;code&gt;postgres:18&lt;&#x2F;code&gt; Docker image from the Docker hub
and run a PostgreSQL container alongside your main web application. The environment
variables defined under the &lt;code&gt;env&lt;&#x2F;code&gt; key will be passed on to the &lt;code&gt;postgres&lt;&#x2F;code&gt; container and
the port&#x2F;directory mapping will be done as defined in the configuration.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;backing-up-postgresql-to-ionos-storage&quot;&gt;Backing up PostgreSQL to Ionos Storage&lt;&#x2F;h2&gt;
&lt;p&gt;If you&#x27;re running a database you should also consider backing it up somewhere. With
PostgreSQL, one simple and easy way is to run &lt;code&gt;pg_dump&lt;&#x2F;code&gt; on your database and store the
result on an S3 bucket.&lt;&#x2F;p&gt;
&lt;p&gt;In the Kamal world, we can use an accessory to do this. The
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siemens&#x2F;postgres-backup-s3&quot;&gt;siemens&#x2F;postgres-backup-to-s3&lt;&#x2F;a&gt; project provides a Docker image to periodically back up
a PostgreSQL database to Amazon S3. We can reference that image to define and boot an
accessory, that, once running, will perform automated database backups for us. It&#x27;s
meant for S3 buckets, but if the object storage provider of your choice is S3
compatible, that should work just as well.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s an example YAML snippet that shows how this can work in practice:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;accessories&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;  postgres-backup&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;    image&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; siemens&#x2F;postgres-backup-s3:18&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;    host&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 1.2.3.4&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;    env&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;      clear&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;        SCHEDULE&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;@daily&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;        BACKUP_KEEP_DAYS&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 7&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;        S3_ENDPOINT&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; https:&#x2F;&#x2F;s3.eu-central-3.ionoscloud.com&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;        S3_REGION&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; eu-central-3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;        S3_BUCKET&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; example&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;        S3_PREFIX&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; backups&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;        POSTGRES_HOST&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; example-postgres&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;        POSTGRES_DATABASE&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; example&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;        POSTGRES_USER&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; example&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;      secret&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; POSTGRES_PASSWORD&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; S3_ACCESS_KEY_ID&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; S3_SECRET_ACCESS_KEY&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We define a &lt;code&gt;postgres-backup&lt;&#x2F;code&gt; accessory that uses the &lt;code&gt;siemens&#x2F;postgres-backup-to-s3&lt;&#x2F;code&gt;
Docker image. The environment variables are all meant for the running container,
defining the backup schedule, the S3 credentials, and details of the database that
should be backed up. This piece of configuration will make sure that our database is
backed up every night and the resulting file is storegd on the configured Ionos storage
bucket.&lt;&#x2F;p&gt;
&lt;p&gt;Note that the &lt;code&gt;S3_ENDPOINT&lt;&#x2F;code&gt; variable does not have anything to do with Amazon S3,
instead referencing an &lt;code&gt;ionoscloud&lt;&#x2F;code&gt; domain name. Ionos storage buckets are S3
compatible, meaning that if you have some code meant to run with S3, you can basically
swap out the bucket URL and the S3 credentials and it should all just continue to work.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;After using Kamal over the past few months, I&#x27;ve come to really like it. It takes away a
lot of the grunt work associated with deploying user-facing web applications. The setup
I described above has been running reliably for my side project, handling both the main
database and the nighly backups without intervention. In case you&#x27;re considering Kamal
for your next deployment, I&#x27;d definitely recommend!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>10 years of personal finances in plain text files</title>
        <published>2025-12-20T00:00:00+00:00</published>
        <updated>2025-12-20T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/10-years-of-personal-finances-in-plain-text-files/"/>
        <id>https://sgoel.dev/posts/10-years-of-personal-finances-in-plain-text-files/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/10-years-of-personal-finances-in-plain-text-files/">&lt;p&gt;January 2026 will mark 10 years since I started storing my personal finances in plain
text files using Beancount. Since January 2016, I&#x27;ve taken out about 30-45 minutes every
single month to download my monthly bank statements and import them into my Beancount
ledger.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s a lot to talk about here, but let&#x27;s start with some fun numbers!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-10-year-old-beancount-ledger&quot;&gt;The 10 year old Beancount ledger&lt;&#x2F;h2&gt;
&lt;p&gt;10 years of financial transactions is a lot of data! All in all, my ledger contains over
45,000 lines of Beancount entries spread across 16 plain text files. All of it is stored
in a &lt;code&gt;finances&lt;&#x2F;code&gt; directory (version controlled) on my laptop. Here&#x27;s a snapshot:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;❯&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; find .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;*.beancount&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; xargs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; wc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -l&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    4037&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; .&#x2F;includes&#x2F;2020.beancount&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    3887&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; .&#x2F;includes&#x2F;2018.beancount&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;      27&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; .&#x2F;includes&#x2F;cash.beancount&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    4398&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; .&#x2F;includes&#x2F;2021.beancount&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    5531&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; .&#x2F;includes&#x2F;2019.beancount&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    5267&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; .&#x2F;includes&#x2F;2022.beancount&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    3287&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; .&#x2F;includes&#x2F;2017.beancount&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    5506&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; .&#x2F;includes&#x2F;2024.beancount&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    5606&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; .&#x2F;includes&#x2F;2023.beancount&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    1454&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; .&#x2F;includes&#x2F;2016.beancount&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    1089&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; .&#x2F;includes&#x2F;open&#x2F;04-expenses.beancount&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;      66&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; .&#x2F;includes&#x2F;open&#x2F;03-income.beancount&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;      11&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; .&#x2F;includes&#x2F;open&#x2F;05-liabilities.beancount&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;      37&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; .&#x2F;includes&#x2F;open&#x2F;02-assets.beancount&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;       1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; .&#x2F;includes&#x2F;open&#x2F;01-equity.beancount&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    4807&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; .&#x2F;main.beancount&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;   45011&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; total&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Running &lt;code&gt;bean-query&lt;&#x2F;code&gt; on &lt;code&gt;main.beancount&lt;&#x2F;code&gt; tells me I have about 10,000 transactions in
total, that in turn contain about 20,000 postings (in double-entry bookkeeping, one
transaction may have multiple postings).&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;❯&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; uv run bean-query main.beancount&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; file: &amp;quot;Goel&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Ready&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; with&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 12466&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; directives&lt;&#x2F;span&gt;&lt;span&gt; (19743&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; postings in&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 9895&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; transactions&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;beanquery&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;There are 1086 accounts in total.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;beanquery&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; select count&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span&gt; (select&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; distinct&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;account&lt;&#x2F;span&gt;&lt;span&gt;));&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;coun&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;----&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;1086&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;... which does not mean that there are 1086 &lt;strong&gt;bank&lt;&#x2F;strong&gt; accounts. Accounts in Beancount are
virtual, and you can create as many as you like. Imagine one account for categorizing
supermarket spending, one for tracking your income, one for your Netflix subscription,
and so on.&lt;&#x2F;p&gt;
&lt;p&gt;Next, there are about 500 documents in the repository.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;❯&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; find documents&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;*.pdf&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; wc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -l&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;     507&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Beancount lets you attach documents (e.g. receipts or invoices) to transactions, that
makes bookkeeping very efficient. I love the fact that whenever I need to do my tax
returns, I can just take a look at my Beancount ledger and find all the invoices right
there next to the relevant transaction.&lt;&#x2F;p&gt;
&lt;p&gt;Lastly, in terms of postings, I started out with 715 in the year 2016, and the year 2023
was the busiest so far in terms of just the total postings count.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;beanquery&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; select year&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;date&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;, count&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; where year&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;date&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 2025&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; group by year&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;date&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;year&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;  coun&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;----&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  ----&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;2016&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;   715&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;2017&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  1422&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;2018&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  1605&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;2019&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  2437&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;2020&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  1582&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;2021&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  2022&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;2022&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  2435&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;2023&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  2651&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;2024&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;  2602&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;the-monthly-ritual&quot;&gt;The Monthly Ritual&lt;&#x2F;h2&gt;
&lt;p&gt;I wrote earlier that every month I take about 30-45 minutes to import my financial
transactions into Beancount. What does that workflow look like? I wrote another, much
more detailed &lt;a href=&quot;https:&#x2F;&#x2F;sgoel.dev&#x2F;posts&#x2F;how-you-can-track-your-personal-finances-using-python&#x2F;&quot;&gt;blog post&lt;&#x2F;a&gt; about it a few years ago, but here&#x27;s a gist.&lt;&#x2F;p&gt;
&lt;p&gt;It starts with me logging in to my bank account(s) to download my monthly statement(s)
in CSV (CSV because it&#x27;s much more predictable to parse compared to PDF). I then run
these CSV files through what&#x27;s called an &quot;importer&quot;, that converts this CSV data into
data structures that Beancount understands. I then append all those extracted entries
into my current &lt;code&gt;.beancount&lt;&#x2F;code&gt; file (which is the main file containing &lt;strong&gt;all&lt;&#x2F;strong&gt; my
financial transactions in plain text). I then go through each entry one by one and make
sure it&#x27;s balanced (in double-entry bookkeeping, all the postings in a transaction must
sum to zero, and not all postings&#x2F;transactions that an importer outputs are balanced).
Some of that balancing is manual and some of it is automated (e.g. the importer code can
look at the transaction&#x27;s description and decide which account it should go into, and
balance automatically). This last part (balancing) is where most of those 30-45 minutes
go.&lt;&#x2F;p&gt;
&lt;p&gt;Whenever a new year starts, I move all the transactions from the past year into a
&lt;code&gt;&amp;lt;year&amp;gt;.beancount&lt;&#x2F;code&gt; file and add an &lt;code&gt;include &amp;lt;year.beancount&amp;gt;&lt;&#x2F;code&gt; in the active
&lt;code&gt;main.beancount&lt;&#x2F;code&gt;file, mostly to avoid the main file from becoming too long. Not that it
would be an issue for Beancount, but just for the sake of readability.&lt;&#x2F;p&gt;
&lt;p&gt;With such a workflow, all my financial transactions from the beginning of time are
contained in a few plain text files in one directory on my laptop.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;building-beancount-importers-for-german-banks&quot;&gt;Building Beancount Importers for German Banks&lt;&#x2F;h2&gt;
&lt;p&gt;Beancount only provides the foundations for working with money, but it has no knowledge
of what your bank statements look like. This is where the concept of an importer comes
in. An importer is a (Python) class that takes in a bank statement of sorts (e.g. a CSV
export of your transactions) and converts them into something that Beancount can work
with.&lt;&#x2F;p&gt;
&lt;p&gt;I live in Germany and my bank accounts are with German banks, so I had to write a few
importers for a few different banks, specifically &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;beancount-dkb&quot;&gt;beancount-dkb&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;beancount-ing&quot;&gt;beancount-ing&lt;&#x2F;a&gt;,
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;beancount-n26&quot;&gt;beancount-n26&lt;&#x2F;a&gt;, and &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;beancount-commerzbank&quot;&gt;beancount-commerzbank&lt;&#x2F;a&gt;. I closed out my Commerzbank account a
while ago, so I don&#x27;t maintain that integration anymore, but the first three
libraries are actively maintained (and used)!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;from-user-to-author&quot;&gt;From User to Author&lt;&#x2F;h2&gt;
&lt;p&gt;My start with Beancount was a bit bumpy. The documentation is very comprehensive but
as a newcomer, I found it tricky to get a grasp on the overall workflow. It took me some
trial and error to figure things out and have that &quot;aha&quot; moment.&lt;&#x2F;p&gt;
&lt;p&gt;I figured that if I found it tricky, maybe it&#x27;s tricky for others as well. So I wrote
a short book to help newcomers get up and running with Beancount easily. If you&#x27;re
interested, here&#x27;s a link: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;personalfinancespython.com&quot;&gt;https:&#x2F;&#x2F;personalfinancespython.com&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The feedback on the book has been super positive. It got mentioned on Beancount&#x27;s
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;beancount.github.io&#x2F;docs&#x2F;external_contributions.html&quot;&gt;external contributions&lt;&#x2F;a&gt; page, and the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;personalfinancespython.com&#x2F;#testimonials&quot;&gt;reader reviews&lt;&#x2F;a&gt; have all been very encouraging!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;closing-thoughts&quot;&gt;Closing Thoughts&lt;&#x2F;h2&gt;
&lt;p&gt;Having all my finances in a bunch of plain text files tracked in a git repository feels
invaluable to me. And hitting the 10 years mark on that almost feels like a milestone.&lt;&#x2F;p&gt;
&lt;p&gt;Perhaps the nicest bit about all this is that this data is sitting on &lt;strong&gt;my own
machine&lt;&#x2F;strong&gt;, not in some data center somewhere else. All of it is in plain text files that
I can open up in my editor, and analyze using the tools that the Beancount ecosystem
gives me. All of it will outlive any app or service, and that, I feel, is why plaintext
accounting is so powerful.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>HTMX and Django make status polling trivial</title>
        <published>2025-11-23T00:00:00+00:00</published>
        <updated>2025-11-23T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/htmx-and-django-make-status-polling-trivial/"/>
        <id>https://sgoel.dev/posts/htmx-and-django-make-status-polling-trivial/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/htmx-and-django-make-status-polling-trivial/">&lt;p&gt;I work on a few projects in my spare time, and in one of them, I recently had to
implement a simple version of status polling. The main application is built using Django
and I don&#x27;t have a lot of tolerance for working with and maintaining JS build pipelines,
so I looked into &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;htmx.org&quot;&gt;htmx&lt;&#x2F;a&gt; to solve the problem.&lt;&#x2F;p&gt;
&lt;p&gt;And I&#x27;m &lt;em&gt;so&lt;&#x2F;em&gt; thankful that I did that. Here&#x27;s a quick writeup.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;background&quot;&gt;Background&lt;&#x2F;h2&gt;
&lt;p&gt;The project in reference is &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;txtcv.com&quot;&gt;txtcv&lt;&#x2F;a&gt;, a plaintext (JSON resume based) CV building and
hosting platform for software developers.&lt;&#x2F;p&gt;
&lt;p&gt;One of the project&#x27;s features is cover letter generation. Given the contents of your CV
and a job description, txtcv is able to generate a cover letter for you. This is
implemented as a background job. Basically, the user clicks on a &quot;generate cover
letter&quot; button that sends a POST request to the server to create an empty &lt;code&gt;CoverLetter&lt;&#x2F;code&gt;
object in the database, that starts a task in the background (using a worker process)
that calls out an LLM to populate the contents of the cover letter. As a result of this
workflow, the status of the cover letter can go from &quot;pending&quot; to &quot;running&quot;, to either
&quot;successful&quot; or &quot;failed&quot;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;problem&quot;&gt;Problem&lt;&#x2F;h2&gt;
&lt;p&gt;Given this workflow, it&#x27;s helpful to let the user know the current status of the cover
letter, so they don&#x27;t need to refresh all the time to know where things stand.&lt;&#x2F;p&gt;
&lt;p&gt;In a typical backend&#x2F;frontend split, this sort of thing would be implemented using an
API returning a JSON representation of the &lt;code&gt;CoverLetter&lt;&#x2F;code&gt; object. The frontend would be
set to poll the backend until the status is one of the final values (either successful
or failed).&lt;&#x2F;p&gt;
&lt;p&gt;The problem is that neither do I have (or want) an API for this purpose, nor do I use a
frontend framework in this project. All the pages in the application are rendered 100%
server-side using Django, and I&#x27;d like to keep things that way, as long as it&#x27;s
possible.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;htmx-django&quot;&gt;htmx + Django&lt;&#x2F;h2&gt;
&lt;p&gt;I had often read about htmx – a library to add dynamic behavior to webpages without any
fuss – but never really had a chance to use it in a project. This problem felt like a
perfect fit for htmx.&lt;&#x2F;p&gt;
&lt;p&gt;So, I installed &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;django-htmx.readthedocs.io&#x2F;en&#x2F;latest&#x2F;&quot;&gt;django-htmx&lt;&#x2F;a&gt; in my project, added a &lt;code&gt;{% htmx_script %}&lt;&#x2F;code&gt; to the base
Django template, and got to work.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;status-polling-with-htmx-and-django&quot;&gt;Status polling with htmx and Django&lt;&#x2F;h2&gt;
&lt;p&gt;It turns out that the workflow for implementing status polling using htmx and Django is
super simple. While there are multiple ways to do it, this section describes just one.&lt;&#x2F;p&gt;
&lt;p&gt;I added a Django view function in the backend that renders a cover letter template
partial (all server side), given a cover letter ID. The Python code looks roughly as
follows:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; django.shortcuts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; get_object_or_404, render&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; app.models&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; CoverLetter&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; render_cover_letter&lt;&#x2F;span&gt;&lt;span&gt;(request, id:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; UUID&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    cover_letter&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; get_object_or_404(CoverLetter,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; render(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        request,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;cover-letter.html&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;cover_letter&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: cover_letter}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Fairly simple. We get a cover letter ID from the URL, render the Django&#x2F;HTML template,
and send it back as a response to the browser.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;cover-letter.html&lt;&#x2F;code&gt; template is where the real magic happens, so let&#x27;s have a look
at that.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;div&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    {% if not cover_letter.has_final_status %}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    hx-trigger&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;every 2s&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    hx-get&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;{% url &amp;#39;cover-letter&amp;#39; id=cover_letter.id %}&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    hx-target&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;this&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    hx-swap&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;outerHTML&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    {% endif %}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; class&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;flex items-start space-x-4 p-6 bg-base-100 rounded-lg border border-base-300&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The code inside the outermost &lt;code&gt;&amp;lt;div&amp;gt;&lt;&#x2F;code&gt; is focused on rendering the cover letter card UI,
which is not important for the purpose of this blog post. The real magic happens through
the conditional &lt;code&gt;hx-&lt;&#x2F;code&gt; attributes added to that outer &lt;code&gt;&amp;lt;div&amp;gt;&lt;&#x2F;code&gt;. Basically, we&#x27;re telling
the browser that if the status of the cover letter is not one of the final values, then
fetch the &lt;code&gt;&#x2F;cover-letter&#x2F;&amp;lt;id&amp;gt;&lt;&#x2F;code&gt; URL every 2 seconds and replace the &lt;code&gt;&amp;lt;div&amp;gt;&lt;&#x2F;code&gt; with the
response from the server. And if the cover letter has a successful or failed status,
then all those &lt;code&gt;hx-&lt;&#x2F;code&gt; attributes are not even rendered in the Django template. All of
this logic is controlled server side, without a single line of frontend JavaScript.&lt;&#x2F;p&gt;
&lt;p&gt;Another neat thing is that whatever the &quot;final&quot; status of the cover letter is, the
frontend doesn&#x27;t need to know or care. At the moment it&#x27;s abstracted into a &lt;code&gt;@property&lt;&#x2F;code&gt;
on the &lt;code&gt;CoverLetter&lt;&#x2F;code&gt; Django model, so external code can just use that property instead
of having to know what exactly those final status values are.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;And there we have it, status polling made trivial through htmx and Django, without a
single line of frontend JS code or build pipelines.&lt;&#x2F;p&gt;
&lt;p&gt;No API endpoints to maintain, no frontend framework to set up, no JavaScript to write.
Just a Django view that renders a template, a few htmx attributes, and the browser
handles the rest. What could have been a complex feature turned out to be super
straightforward, thanks to htmx.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Building a CV builder</title>
        <published>2025-11-01T00:00:00+00:00</published>
        <updated>2025-11-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/building-a-cv-builder/"/>
        <id>https://sgoel.dev/posts/building-a-cv-builder/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/building-a-cv-builder/">&lt;p&gt;A few weeks ago, I started playing around with the idea of developing a CV builder,
specifically for software developers. It came out of a personal frustration that I&#x27;ve
had for a long time now, which is, the options available to me to write and maintain my
CV are honestly not very good.&lt;&#x2F;p&gt;
&lt;p&gt;For the longest time, I kept my CV in LaTeX, which is something I really don&#x27;t want to
touch anymore. First of all, the language itself is not very intuitive. Next, installing
gigabytes worth of LaTeX packages locally just to compile a text file to a PDF is not
something I like doing very often. And I haven&#x27;t even gotten to installing external
packages (such as themes) yet. Overall, LaTeX hasn&#x27;t been a very pleasant experience, so
I&#x27;m ruling it out for the foreseeable future for maintaining my CV.&lt;&#x2F;p&gt;
&lt;p&gt;I then switched over to using a third-party app, which was okayish, except that they
charged me something like $20 per month. I generally don&#x27;t mind paying for services on
the internet where I feel I&#x27;m getting enough value, but in this specific case it felt a
bit too much. Besides, the whole experience felt very non-portable to me. In the end, I
saved all my CV data on someone else&#x27;s website, making my own CV opaque from myself.&lt;&#x2F;p&gt;
&lt;p&gt;Finally, I switched over to keeping my CV in a Google Doc, which kinda sorta works, but
every single time I want to update it, I inevitably end up adjusting things like
document margins, which is annoying.&lt;&#x2F;p&gt;
&lt;p&gt;So, I&#x27;m now working on shaving this yak.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m working on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;txtcv.com&quot;&gt;txtcv&lt;&#x2F;a&gt; to help folks in tech (including myself) build and maintain their
CVs. Here are the top three highlights from the project:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;txtcv is portable, using plaintext files to store CV data. &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;jsonresume.org&#x2F;&quot;&gt;JSON Resume&lt;&#x2F;a&gt; is a pretty
nice and mostly complete schema to represent CVs. txtcv only accepts CVs written in
JSON (YAML support is in the pipeline) that comply with the JSON Resume schema. At
all times, the data remains in your control. Put it in a git repository or edit it
using the web interface – it doesn&#x27;t really care. You can get the JSON out anytime
you want.&lt;&#x2F;li&gt;
&lt;li&gt;txtcv is cheap. The basic plan is free and it lets you maintain one CV. If you want
more fancy features (e.g. CVs tailored to job description, mark your CV web link as
private, AI-assisted cover-letter drafting etc.), the subscription plans start at $25
per year at the time of this writing, which comes down to roughly $2 per month.
You&#x27;re probably paying a lot more for coffee &lt;em&gt;every day&lt;&#x2F;em&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;txtcv has first-class developer integration. There is a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;txtcv.com&#x2F;cli&#x2F;&quot;&gt;CLI&lt;&#x2F;a&gt; you can install locally
or in Github Actions (more Git providers are in the pipeline) to &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;txtcv&#x2F;actions-validate&quot;&gt;validate&lt;&#x2F;a&gt; and
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;txtcv&#x2F;actions-publish&quot;&gt;publish&lt;&#x2F;a&gt; your CV from anywhere you want.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;If this piques your interest, head over to &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;txtcv.com&quot;&gt;https:&#x2F;&#x2F;txtcv.com&lt;&#x2F;a&gt;, sign in using your
Github account, and set up your first CV. Install the CLI via Homebrew if you&#x27;d rather
drive the whole thing from the command line. And of course, feel free to follow the
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;txtcv.com&#x2F;blog&#x2F;&quot;&gt;txtcv blog&lt;&#x2F;a&gt; and &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;bsky.app&#x2F;profile&#x2F;txtcv.com&quot;&gt;@txtcv.com&lt;&#x2F;a&gt; Bluesky account for the latest updates.&lt;&#x2F;p&gt;
&lt;p&gt;The project is still very early, so every bit of feedback helps shape what it becomes!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Notes from my first marathon</title>
        <published>2025-10-13T00:00:00+00:00</published>
        <updated>2025-10-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/notes-from-my-first-marathon/"/>
        <id>https://sgoel.dev/posts/notes-from-my-first-marathon/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/notes-from-my-first-marathon/">&lt;p&gt;I ran my first marathon in &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;marathonmuenchen.org&#x2F;en&#x2F;&quot;&gt;Munich&lt;&#x2F;a&gt; yesterday! The experience was incredible and the
crowd was overflowing with energy. It honestly felt like one giant party across the
city. I thought I&#x27;ll reflect a little bit on my first time running 42km, covering the
three biggest learnings and my main takeaway, as long as everything is still fresh in my
mind.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;a-good-training-plan-is-critical&quot;&gt;A good training plan is critical.&lt;&#x2F;h3&gt;
&lt;p&gt;All in all, I trained for about 3 months with a local running coach who
prepared a training plan for me based on my current (at that time) running status.&lt;&#x2F;p&gt;
&lt;p&gt;The plan was in the form of an Excel sheet that told me exactly what kind of a run
(distance, intervals, etc.) I needed to do on any given day, including rest days,
strength training days, and so on. Looking back, I feel that having this plan was
critical. It was still possible for me to talk to the coach to move things around if
there was a need, but generally, I really liked having this predictability. Plus, it was
really motivating to do the training for the day and put a checkmark in the excel sheet
when I was done!&lt;&#x2F;p&gt;
&lt;p&gt;And no, it really wasn&#x27;t expensive to get such a plan made. Gym memberships cost a lot
more.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;listen-to-your-body&quot;&gt;Listen to your body.&lt;&#x2F;h3&gt;
&lt;p&gt;Before I started training, there were a few months where I couldn&#x27;t find any time at all
to go running. This meant that I went from no running to running ~15km within just a few
days.&lt;&#x2F;p&gt;
&lt;p&gt;This was incredibly stressful on my knee and I ended up developing a bit of a runner&#x27;s
knee situation. Thankfully, I noticed this very soon and went for a few physiotherapy
sessions, which made a big difference very quickly. Things would&#x27;ve been much worse
otherwise!&lt;&#x2F;p&gt;
&lt;p&gt;Similarly, about 2 weeks before the marathon day, I caught a cold and had to skip a few
training runs, one of them being a ~30km. I had already done a 30km run the weekend
before that one, but this would&#x27;ve been the second time and I was really looking forward
to running a 30km again, but couldn&#x27;t because of the sickness. The only choice I had was
to sit it out and let the body recover.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re in the middle of your training and your body is asking you to do something,
listen!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;mental-resilience-is-just-as-important&quot;&gt;Mental resilience is just as important.&lt;&#x2F;h3&gt;
&lt;p&gt;Training for and running a marathon requires not just physical strength but also strong
mental resilience.&lt;&#x2F;p&gt;
&lt;p&gt;During the training phase, there were days when going for a run felt like the last thing
I wanted to do, whether due to bad weather or simply not being in the mood. Even during
the marathon (especially between kilometers 24 and 30), almost every few minutes I could
hear my mind tell me to give up and jump out of the race, or just walk the remaining
distance because that&#x27;s the easier thing to do.&lt;&#x2F;p&gt;
&lt;p&gt;The mind plays tricks, but all of that is normal. Part of the training is to ignore all
those voices, and just keep going.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;My biggest takeaway from training for and running the marathon is learning just how
resilient and malleable the human body is. Every time you push your limits, the body
initially feels exhausted. However, if you do it again, it quickly becomes the new
normal.&lt;&#x2F;p&gt;
&lt;p&gt;Being able to do a marathon has been one of the hardest but also one of the
most rewarding experiences I&#x27;ve had so far. And I can&#x27;t wait to recover and sign up for
my next one!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Good Lazy, Bad Lazy: AI usage reflections</title>
        <published>2025-09-27T00:00:00+00:00</published>
        <updated>2025-09-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/good-lazy-bad-lazy-ai-usage-reflections/"/>
        <id>https://sgoel.dev/posts/good-lazy-bad-lazy-ai-usage-reflections/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/good-lazy-bad-lazy-ai-usage-reflections/">&lt;p&gt;It&#x27;s been a while since I integrated AI agents into my programming workflow. At work I
mainly use Claude Code. For private projects I&#x27;m a cheapskate, which means I use
whatever is the cheapest (which at the time of this writing happens to be Gemini, given
their very generous free plan).&lt;&#x2F;p&gt;
&lt;p&gt;After about a year of using them almost on a daily basis, I&#x27;ve noticed my AI usage falls
into two (generally) distinct camps.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;camp-1-productivity-gains&quot;&gt;Camp 1: Productivity Gains&lt;&#x2F;h3&gt;
&lt;p&gt;The first camp is what I&#x27;d call productivity gains. This is when I offload the type of
work that&#x27;s necessary but is either rote work or is somewhat at the edge of my
domain knowledge.&lt;&#x2F;p&gt;
&lt;p&gt;For instance, if I&#x27;m working on a fullstack feature and want to make the UI look good
(building UIs not being my core skill), I may use an AI agent to take a first pass
at the implementation. This saves me a few rounds of Google searches, copy&#x2F;pasting
suggestions from StackOverflow, etc.&lt;&#x2F;p&gt;
&lt;p&gt;Although implementing good looking UIs may not be my core skill, I&#x27;m still fairly
confident at distinguishing a good implementation from a bad one, which means I have a
very good idea of what I&#x27;m looking for. Using AI agents to help me with such problems
saves me time and increases the scope of what I&#x27;m capable of building, in turn making me
more productive.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;camp-2-good-lazy&quot;&gt;Camp 2: Good Lazy&lt;&#x2F;h3&gt;
&lt;p&gt;The second (more interesting) camp is when I&#x27;m being plain lazy, but of the good kind!&lt;&#x2F;p&gt;
&lt;p&gt;A good example here is creating pull requests on Github. At work, I wrote a custom
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.claude.com&#x2F;en&#x2F;docs&#x2F;claude-code&#x2F;slash-commands#custom-slash-commands&quot;&gt;slash command&lt;&#x2F;a&gt; for Claude Code to create PRs on Github. Instructions for this command
ask Claude to take a look at the diff between the main development branch and the
current branch, and create a PR on Github using the &lt;code&gt;gh&lt;&#x2F;code&gt; CLI, assigning it a title and
description based on the git diff and formatting the description based on the PR
template checked in to the git repository.&lt;&#x2F;p&gt;
&lt;p&gt;Of course, I could easily do all those things myself, and the PR&#x27;s description would be
a bit more focused (LLMs tend to blabber a lot!), having a custom command for this saves
me time. I&#x27;m being lazy, but I&#x27;d argue that this is the good lazy.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;At the end of the day, I feel that when using AI agents, it&#x27;s very important to know the
difference between genuine productivity gains and shortcuts to complacency. While the
two of them are not mutually exclusive, comfort that leads to complacency isn&#x27;t
something desirable in the long run. Hopefully this quick reflection of mine encourages
you to be mindful of your own workflows with AI agents!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Starting fatherhood</title>
        <published>2025-02-21T00:00:00+00:00</published>
        <updated>2025-02-21T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/starting-fatherhood/"/>
        <id>https://sgoel.dev/posts/starting-fatherhood/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/starting-fatherhood/">&lt;p&gt;Earlier this week, my wife and I welcomed our tiny little daughter in this world. As
parents, we&#x27;re very happy and completely smitten by this new addition to our lives.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m barely a week into fatherhood, so there&#x27;s a lot to learn. I didn&#x27;t fully grasp what
people meant when they said that becoming a parent changes you at a fundamental level.
Now I think I&#x27;m beginning to understand. Taking care of a newborn is a responsibility
unlike anything else I&#x27;ve experienced. It&#x27;s challenging, has a steep learning curve, and
the stakes are high. Yet, at the end of the day, when you see that tiny human look at
you and smile, nothing else matters and you feel like you&#x27;d gladly do it all over again.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Trying out Zed after more than a decade of Vim&#x2F;Neovim</title>
        <published>2025-01-25T00:00:00+00:00</published>
        <updated>2025-01-25T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/trying-out-zed-after-more-than-a-decade-of-vim-neovim/"/>
        <id>https://sgoel.dev/posts/trying-out-zed-after-more-than-a-decade-of-vim-neovim/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/trying-out-zed-after-more-than-a-decade-of-vim-neovim/">&lt;p&gt;I&#x27;m currently drafting this blog post in &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zed.dev&quot;&gt;Zed&lt;&#x2F;a&gt;. After using Vim&#x2F;Neovim for more than 15
years, I recently decided to try out something new. I don&#x27;t know if this little
experiment will work, or if I&#x27;ll run back to my trusty Neovim, but hey, what I do know
is that I&#x27;d like to find out.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-try-something-else&quot;&gt;Why try something else?&lt;&#x2F;h2&gt;
&lt;p&gt;Why though? If you&#x27;ve used a specific tool for that long, why (try to) switch to
something else anyway? Everyone has their own reasons. I have two.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;1-the-desire-to-use-something-that-just-works&quot;&gt;1. The desire to use something that just works&lt;&#x2F;h3&gt;
&lt;p&gt;Lately, I&#x27;ve been drifting towards things that &quot;just work&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;I love (Neo)Vim. Like I wrote earlier, it&#x27;s been my primary editor for the past 15
years. What I don&#x27;t love is all the configuration that goes into it before I can use it
to start writing code.&lt;&#x2F;p&gt;
&lt;p&gt;When I first started using Vim (in 2009!), my configuration was a tiny &lt;code&gt;.vimrc&lt;&#x2F;code&gt; file
that was handed over to me by my internship supervisor at that time. I had no idea what
it did, but it worked. Over time, as I felt the need to customize things, my &lt;code&gt;.vimrc&lt;&#x2F;code&gt;
started accumulating increasingly more code copied from StackOverflow that I didn&#x27;t
understand.&lt;&#x2F;p&gt;
&lt;p&gt;At some point Neovim came out, and with it, the ability to configure things using Lua.
This was a huge step up, because I could at least understand what I was configuring.&lt;&#x2F;p&gt;
&lt;p&gt;Either way, it&#x27;s still configuration. What changed was just &lt;em&gt;how&lt;&#x2F;em&gt; that configuration was
done. The combination of configuring the base editor and installing a set of plugins
(and making sure that they play nice with each other) isn&#x27;t really something that I
would like to spend my time on, going forward.&lt;&#x2F;p&gt;
&lt;p&gt;One recent example that highlights this problem: my workflow consists of switching
back and forth between a terminal window and an editor window. At any time, I have
multiple projects open in both. On macOS, it&#x27;s been difficult to find a solution that
&quot;just works&quot;. The most popular Neovim GUI clients on macOS include &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;qvacua&#x2F;vimr&quot;&gt;Vimr&lt;&#x2F;a&gt; and &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;neovide.dev&#x2F;&quot;&gt;Neovide&lt;&#x2F;a&gt;,
which are both excellent projects. Neovide though, &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;neovide&#x2F;neovide&#x2F;issues&#x2F;1332&quot;&gt;does not support multiple windows&lt;&#x2F;a&gt;,
which is integral to my workflow and hence requires me to implement &lt;a href=&quot;https:&#x2F;&#x2F;sgoel.dev&#x2F;posts&#x2F;switching-between-projects-in-neovim&#x2F;&quot;&gt;workarounds&lt;&#x2F;a&gt;, which
actually broke last week after I ran &lt;code&gt;:PackerUpdate&lt;&#x2F;code&gt;. And while Vimr does support
multiple windows, it is &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;qvacua&#x2F;vimr&#x2F;issues&#x2F;887&quot;&gt;not able to render icons in nvim-tree&lt;&#x2F;a&gt;, which is less than
ideal. 🤷🏻‍♂️&lt;&#x2F;p&gt;
&lt;h3 id=&quot;2-deeper-and-native-llm-integration&quot;&gt;2. Deeper and native LLM integration&lt;&#x2F;h3&gt;
&lt;p&gt;LLMs are happening, whether we like it or not. And no, the fact that they aren&#x27;t 100%
correct all the time is not a reason to discard them entirely. I understand why some
people don&#x27;t want to use them. And I respect that. I personally find them useful and
would like to integrate them more into my daily workflow.&lt;&#x2F;p&gt;
&lt;p&gt;The Neovim ecosystem has a bunch of plugins for using LLMs. But as I wrote in the
previous section, I&#x27;d like to avoid plugins when possible. Installing plugins means
updating them, which inevitably breaks things.&lt;&#x2F;p&gt;
&lt;p&gt;One example where I find LLMs useful when coding is handling boilerplate stuff. In my
current Neovim setup, I have the the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;Exafunction&#x2F;codeium.nvim&quot;&gt;Codeium extension&lt;&#x2F;a&gt; installed, which offers code
suggestions that are like an autocomplete on steroids. Often, I can write a function
name and what parameters it&#x27;s expecting, and the LLM writes out the function for me.
This is particularly helpful when writing small functions or test cases.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;d like to be able to do more of such things in my editor. I&#x27;m not entirely sold on the
idea of agentic editors, as I&#x27;d like to keep at least &lt;em&gt;some&lt;&#x2F;em&gt; agency over the code that&#x27;s
going in, but that&#x27;s a different topic.&lt;&#x2F;p&gt;
&lt;p&gt;Another example of where I find LLMs useful: I&#x27;m not a native English speaker and often
struggle to find the right sentence framing that is both concise and catchy. LLMs are
excellent at this! I can scribble my initial thoughts, throw it at an LLM and ask it to
reframe the whole thing, which is great!&lt;&#x2F;p&gt;
&lt;p&gt;Generally, it feels like the way we&#x27;re writing code is changing with the introduction of
this extremely powerful tool, and I&#x27;d like to move with the times.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-zed&quot;&gt;Why Zed?&lt;&#x2F;h2&gt;
&lt;p&gt;So yeah, those are the two reasons why I&#x27;ve been looking to try out something else
lately. What made me go for Zed?&lt;&#x2F;p&gt;
&lt;h3 id=&quot;1-vim-mode&quot;&gt;1. Vim mode&lt;&#x2F;h3&gt;
&lt;p&gt;Yep, that&#x27;s literally the first reason. After using Vim for 15 years, my fingers have
built up enough muscle memory that not using Vim keybindings when writing code is
downright impossible.&lt;&#x2F;p&gt;
&lt;p&gt;Zed&#x27;s &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zed.dev&#x2F;docs&#x2F;vim&quot;&gt;Vim mode&lt;&#x2F;a&gt; is surprisingly solid! So far, I feel right at home. Almost all Vim
keybindings that I&#x27;m used to work just as expected. The one or two bindings that don&#x27;t
work, are something that I can make my peace with and retrain my fingers on. Everything
else works excellent. It also looks like they&#x27;re &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zed.dev&#x2F;blog&#x2F;vim-2025&quot;&gt;doubling down on their Vim mode&lt;&#x2F;a&gt;
support in 2025, which is a great sign!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;2-it-just-works&quot;&gt;2. It just works&lt;&#x2F;h3&gt;
&lt;p&gt;When you fire up Zed, the editor is fully functional without having to write a single
line of configuration. That&#x27;s awesome. The editor may prompt you every now and then to
install support for specific languages. But that&#x27;s usually just one button click.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;3-json-less-configuration&quot;&gt;3. &lt;del&gt;JSON&lt;&#x2F;del&gt; Less configuration&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;em&gt;Update: this one started a bit of controversy on internet water coolers so I&#x27;ll
elaborate a little more.&lt;&#x2F;em&gt; 🙃&lt;&#x2F;p&gt;
&lt;p&gt;Lua is great, but one JSON file is even better. Zed uses JSON as the configuration
syntax and so far it feels a lot simpler than what I&#x27;ve worked with so far.&lt;&#x2F;p&gt;
&lt;p&gt;The underlying sentiment here is that I&#x27;m looking to have &lt;strong&gt;less&lt;&#x2F;strong&gt; configuration. I
don&#x27;t really care whether the format is JSON, or YAML, or TOML, or whatever else. I&#x27;m
actively trying to do less dynamic things in my configuration, so I&#x27;m beginning to
appreciate the fact that the text editor gives me less buttons to play with.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;4-configuration-autocomplete&quot;&gt;4. Configuration autocomplete&lt;&#x2F;h3&gt;
&lt;p&gt;I had no idea I needed this until I had it!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;sgoel.dev&#x2F;posts&#x2F;trying-out-zed-after-more-than-a-decade-of-vim-neovim&#x2F;zed-configuration-autocomplete.png&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;While editing the Zed configuration file, I found it really helpful how the editor
suggests configuration keys and their potential values. This feature felt really
thoughtful when I first saw it and I could imagine that it goes a long way in getting
new users up to speed quicker.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;5-native-llm-integration&quot;&gt;5. Native LLM integration&lt;&#x2F;h3&gt;
&lt;p&gt;I know some people have been turned off by this, but I actually like this feature.&lt;&#x2F;p&gt;
&lt;p&gt;Zed has a feature called Assistant, which is a tool to, well, assist you, using a large
language model of your choice. A few popular LLM providers are supported. Once set up,
you can open up the Assistant panel to work together with the language model you&#x27;ve
chosen. For instance, it&#x27;s pretty easy to type in questions in the panel window and then
paste text from your open file buffers into it to give the LLM more context. There&#x27;s a
lot more to this feature that I haven&#x27;t used yet, but I&#x27;ll probably get to it the more I
use it.&lt;&#x2F;p&gt;
&lt;p&gt;Overall, the integration feels very native and very useful.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;6-fast&quot;&gt;6. Fast!&lt;&#x2F;h3&gt;
&lt;p&gt;Last but definitely not the least is speed! Zed is &lt;em&gt;very&lt;&#x2F;em&gt; fast. Everything feels very
snappy, and it&#x27;s clear that the team has put a lot of effort into making everything
fast.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;It hasn&#x27;t been &lt;em&gt;that&lt;&#x2F;em&gt; long since I really started using Zed instead of Neovim as my
daily driver. And so far the experience has been quite nice.&lt;&#x2F;p&gt;
&lt;p&gt;Like I wrote earlier, this is an experiment. We&#x27;ll see how things turn out. That being
said though, the first impression looks &lt;strong&gt;very&lt;&#x2F;strong&gt; good.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Building Cython (or C) extensions using uv</title>
        <published>2025-01-11T00:00:00+00:00</published>
        <updated>2025-01-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/building-cython-or-c-extensions-using-uv/"/>
        <id>https://sgoel.dev/posts/building-cython-or-c-extensions-using-uv/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/building-cython-or-c-extensions-using-uv/">&lt;p&gt;Building and maintaining Python libraries with extension modules can be somewhat tricky.
I maintain one such library called &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;streaming-form-data&quot;&gt;streaming-form-data&lt;&#x2F;a&gt;, that provides a streaming
parser for multipart&#x2F;form-data input and has all the performance-critical code written
using Cython.&lt;&#x2F;p&gt;
&lt;p&gt;In this post I&#x27;ll share my experiences transitioning this library from Poetry to uv. If
you also maintain such a library or are just plain curious about how uv can work
together with Cython, you may find this post useful.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-transition-to-uv&quot;&gt;Why transition to uv?&lt;&#x2F;h3&gt;
&lt;p&gt;For years, I&#x27;ve been using Poetry for &lt;code&gt;streaming-form-data&lt;&#x2F;code&gt;, together with a custom
&lt;code&gt;build.py&lt;&#x2F;code&gt; script for handling the C extension. While this setup is not explicitly
documented &#x2F; supported by Poetry, it has worked really well so far.&lt;&#x2F;p&gt;
&lt;p&gt;Last week though, the release of Poetry 2.0.0 introduced a bug that broke my build
process, and introduced installation errors for some users of the library. After
spending some time debugging the issue, I realized that this issue would be best fixed
upstream (at Poetry&#x27;s end). At the same time I&#x27;ve been looking into &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;astral.sh&#x2F;uv&#x2F;&quot;&gt;uv&lt;&#x2F;a&gt; with curiosity
for a few months now, as I&#x27;m sure most of the members in the Python community have been.
I wasn&#x27;t actively looking to move away from Poetry, but this seemed like a good
opportunity to try uv out!&lt;&#x2F;p&gt;
&lt;p&gt;The move went pretty well! I published version 1.19.1 of the package yesterday which was
the first version built using uv!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;uv-and-build-backends&quot;&gt;uv and build backends&lt;&#x2F;h3&gt;
&lt;p&gt;uv is the latest tool in the Python ecosystem that has taken almost the entire community
by storm. It&#x27;s &lt;strong&gt;extremely&lt;&#x2F;strong&gt; fast. It consolidates multiple tools into one. It can
manage the installation of multiple Python distributions for you. And did I mention that
it&#x27;s fast?&lt;&#x2F;p&gt;
&lt;p&gt;uv also allows building and publishing packages to PyPI, delegating the actual package
building to something called a &quot;build backend&quot;. Popular build backend choices at the
time of this writing include &lt;code&gt;hatchling&lt;&#x2F;code&gt; (ideal for pure Python projects), &lt;code&gt;setuptools&lt;&#x2F;code&gt;
(versatile and battle-tested choice that supports C extensions), &lt;code&gt;flit&lt;&#x2F;code&gt; (focused on
simplicity for pure Python projects), and a few others.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;setuptools&lt;&#x2F;code&gt; remains a solid choice for projects that use Cython or include a pure C
extension. Despite misconceptions about its status (no, it&#x27;s not deprecated),
&lt;code&gt;setuptools&lt;&#x2F;code&gt; continues to evolve and is actively maintained by the PyPA.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s also what I ended up going with for &lt;code&gt;streaming-form-data&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;cython&quot;&gt;Cython&lt;&#x2F;h3&gt;
&lt;p&gt;Cython is a superset of Python that gives you the ability to compile your Python code
into optimized C code, which can then be included as a normal C extension into your
library. There are several ways of distributing Cython code in libraries. I personally
use the following approach:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Compile the &lt;code&gt;.pyx&lt;&#x2F;code&gt; source code into &lt;code&gt;.c&lt;&#x2F;code&gt; using the &lt;code&gt;cython&lt;&#x2F;code&gt; CLI.&lt;&#x2F;li&gt;
&lt;li&gt;Include the generated &lt;code&gt;.c&lt;&#x2F;code&gt; file into the source distribution, so that end users don&#x27;t
need to have Cython installed.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;I&#x27;ve been using this approach for quite a few years now and haven&#x27;t had any reasons to
complain so far.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;pyproject-toml-configuration&quot;&gt;pyproject.toml configuration&lt;&#x2F;h3&gt;
&lt;p&gt;Here&#x27;s how I configured all the above in the project&#x27;s &lt;code&gt;pyproject.toml&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;build-system&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;requires = [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;setuptools&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;build-backend =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;setuptools.build_meta&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;tool&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;setuptools&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;package-dir = {&amp;quot;&amp;quot; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;src&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ext-modules = [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {name =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;streaming_form_data._parser&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, sources = [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;src&#x2F;streaming_form_data&#x2F;_parser.c&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;build-system&lt;&#x2F;code&gt; section defines what build backend to use. &lt;code&gt;package-dir&lt;&#x2F;code&gt; in
&lt;code&gt;tool.setuptools&lt;&#x2F;code&gt; maps the package directory to &lt;code&gt;src&lt;&#x2F;code&gt;, aligning with modern Python
packaging recommendations. And finally the &lt;code&gt;ext-modules&lt;&#x2F;code&gt; list defines the C extension to
be built, using the &lt;code&gt;.c&lt;&#x2F;code&gt; file generated by Cython.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h3&gt;
&lt;p&gt;In my case, the transition to uv has worked quite well!&lt;&#x2F;p&gt;
&lt;p&gt;One standout feature is its remarkable speed. For instance, in my Github Actions
pipeline, the step for installing the package dependencies now takes only 7 seconds,
down from 14 earlier. Resolving packages locally is almost instantaneous, and the entire
experience is incredibly snappy. What I appreciate most, though, is the simplicity  of
managing just one tool (uv) instead of juggling both pyenv and Poetry.&lt;&#x2F;p&gt;
&lt;p&gt;Beyond its performance, uv is rapidly gaining traction in the Python community. While I
typically wait a few months before switching to the shiny new object, this time, the
switch has been worth it. If you&#x27;re considering making the move, uv is a great choice!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Moving from Beancount 2.x to 3.x</title>
        <published>2024-07-10T00:00:00+00:00</published>
        <updated>2024-07-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/moving-from-beancount-2x-to-3x/"/>
        <id>https://sgoel.dev/posts/moving-from-beancount-2x-to-3x/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/moving-from-beancount-2x-to-3x/">&lt;p&gt;A few days ago, Beancount &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;groups.google.com&#x2F;g&#x2F;beancount&#x2F;c&#x2F;iTdRuvZnE4E&quot;&gt;officially switched&lt;&#x2F;a&gt; from 2.x to 3.x. If I&#x27;m being honest, I
was a bit afraid of this release, mostly because it was supposed to be a major rewrite.
I&#x27;m generally not a huge fan of rewrites, and I don&#x27;t think I&#x27;m the only one on the
Internet who thinks that way.&lt;&#x2F;p&gt;
&lt;p&gt;Luckily, the update seems to have been less of an event as I anticipated. If you&#x27;re
planning to move from Beancount 2.x to 3.x, the update should be &lt;em&gt;mostly&lt;&#x2F;em&gt; uneventful.
Nevertheless, there are a few important changes that may affect your workflow. The rest
of this blog post will summarize the ones that I&#x27;ve noticed so far.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;1-external-data-import-workflow&quot;&gt;1. External data import workflow&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Update (May 2025)&lt;&#x2F;strong&gt;: The original version of this blog post that I published in July
2024 described only the pyproject.toml-based approach of importing external data. Since
then, I&#x27;ve discovered another (somewhat more official) way of importing external data,
using &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pypi.org&#x2F;project&#x2F;beangulp&#x2F;&quot;&gt;beangulp&lt;&#x2F;a&gt;. Although the official documentation does not seem to reference it yet,
beangulp provides a few examples that use the &lt;code&gt;Ingest&lt;&#x2F;code&gt; class to set up a workflow that
is not so different from the older &lt;code&gt;config.py&lt;&#x2F;code&gt; based workflow. For completeness, I&#x27;ll
describe both the approaches here.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The first thing I noticed is that the workflow for importing external data looks
different.&lt;&#x2F;p&gt;
&lt;p&gt;With 2.x, you had to maintain a &lt;code&gt;config.py&lt;&#x2F;code&gt; file in your project root where you would
import all your Importer classes and put them inside a &lt;code&gt;CONFIG&lt;&#x2F;code&gt; variable.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; first_bank_importer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; FirstImporter&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; second_bank_importer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; SecondImporter&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;CONFIG&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [FirstImporter(), SecondImporter()]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The Beancount commands like &lt;code&gt;bean-extract&lt;&#x2F;code&gt; and &lt;code&gt;bean-identify&lt;&#x2F;code&gt; would then pick this file
and run through the list of specified importers to find the matching one.&lt;&#x2F;p&gt;
&lt;p&gt;In 3.x, this workflow is gone. Commands like &lt;code&gt;bean-extract&lt;&#x2F;code&gt; and &lt;code&gt;bean-identify&lt;&#x2F;code&gt; have
been removed from the &lt;code&gt;beancount&lt;&#x2F;code&gt; package. Instead, the project has decided to go for a
script-based workflow instead (&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.google.com&#x2F;document&#x2F;d&#x2F;1O42HgYQBQEna6YpobTqszSgTGnbRX7RdjmzR2xumfjs&#x2F;edit#heading=h.hjzt0c6v8pfs&quot;&gt;source&lt;&#x2F;a&gt;). It appears that you don&#x27;t need to maintain a
&lt;code&gt;config.py&lt;&#x2F;code&gt; anymore, and your importers need to provide a command-line entry point that
can do those tasks (eg. identifying whether a given file matches an importer or
extracting a list of transactions out of a file).&lt;&#x2F;p&gt;
&lt;p&gt;If you maintain custom importers and distribute them through PyPI, the one immediate
downside of this approach is how those importers are initialized. With the &lt;code&gt;config.py&lt;&#x2F;code&gt;
approach, your users could instantiate importers just as any Python code, because it was
literally just Python code. Here&#x27;s an example of how that used to look like:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; my_importer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; MyImporter&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;CONFIG&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    MyImporter(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;        name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;Assets:MyBank:Checking&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;        currency&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;EUR&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;        patterns&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;ALDI&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Expenses:Supermarket:ALDI&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;},&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In the absence of &lt;code&gt;config.py&lt;&#x2F;code&gt;, you&#x27;ll need to find a different way of doing this.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;1-using-beangulp-ingest&quot;&gt;1. Using &lt;code&gt;beangulp.Ingest&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;beangulp&lt;&#x2F;code&gt; provides an &lt;code&gt;Ingest&lt;&#x2F;code&gt; class that accepts a list of importer objects,
ultimately giving you a workflow similar to what was available using &lt;code&gt;config.py&lt;&#x2F;code&gt;. The
idea is to define an &lt;code&gt;import.py&lt;&#x2F;code&gt; script (the script&#x27;s name can be anything) in your
project root that contains something like the following:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; my_importer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; MyImporter&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; beangulp&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; Ingest&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;importers&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    MyImporter(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;        name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;Assets:MyBank:Checking&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;        currency&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;EUR&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;        patterns&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;ALDI&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Expenses:Supermarket:ALDI&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;},&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; __name__&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;__main__&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ingest&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; Ingest(importers)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ingest()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Running this script should produce the following output on the console:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Usage: import.py [OPTIONS] COMMAND [ARGS]...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Import data from and file away documents from financial institutions.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Options:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --version  Show the version and exit.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --help     Show this message and exit.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Commands:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  archive   Archive documents.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  extract   Extract transactions from documents.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  identify  Identify files for import.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;As you can see, the script now supports &lt;code&gt;archive&lt;&#x2F;code&gt;, &lt;code&gt;extract&lt;&#x2F;code&gt;, and &lt;code&gt;identify&lt;&#x2F;code&gt;
subcommands, that replace &lt;code&gt;bean-file&lt;&#x2F;code&gt;, &lt;code&gt;bean-extract&lt;&#x2F;code&gt;, and &lt;code&gt;bean-identify&lt;&#x2F;code&gt;,
respectively.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;2-defining-importer-specific-clis&quot;&gt;2. Defining importer-specific CLIs&lt;&#x2F;h3&gt;
&lt;p&gt;If you don&#x27;t want to use &lt;code&gt;beangulp.Ingest&lt;&#x2F;code&gt;, another approach is put the importer&#x27;s
initialization parameters inside &lt;code&gt;pyproject.toml&lt;&#x2F;code&gt;. The &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;packaging.python.org&#x2F;en&#x2F;latest&#x2F;guides&#x2F;writing-pyproject-toml&#x2F;&quot;&gt;&lt;code&gt;pyproject.toml&lt;&#x2F;code&gt; file&lt;&#x2F;a&gt; is
a relatively new Python standard that is used by a few other tools in the Python
ecosystem to place their configuration bits (eg. inside a &lt;code&gt;tool.X&lt;&#x2F;code&gt; section), amongst
other information. The reasoning here is that if you&#x27;re using Beancount, your finances
project is basically a Python project, that may as well contain a &lt;code&gt;pyproject.toml&lt;&#x2F;code&gt;
file, which in turn means putting importer configuration in there isn&#x27;t all that bad.&lt;&#x2F;p&gt;
&lt;p&gt;As an example, if you maintain a &lt;code&gt;beancount-mybank&lt;&#x2F;code&gt; package distributed through PyPI,
you could ask your users to put the following snippet inside their &lt;code&gt;pyproject.toml&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[tool.beancount-mybank.ec]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;name = &amp;quot;Assets:MyBank:Checking&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;currency = &amp;quot;EUR&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;patterns = [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    [&amp;quot;ALDI&amp;quot;, &amp;quot;Expenses:Supermarket:ALDI&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;... and add a &lt;code&gt;beancount-mybank-ec&lt;&#x2F;code&gt; CLI command as part of your &lt;code&gt;beancount-mynank&lt;&#x2F;code&gt;
package. This CLI command could read the configuration inside &lt;code&gt;pyproject.toml&lt;&#x2F;code&gt;,
initialize the importer, and then call out &lt;code&gt;identify&lt;&#x2F;code&gt; or &lt;code&gt;extract&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Of course, this is just one way of approaching things. I&#x27;m sure there are more ways to
approach this problem.&lt;&#x2F;p&gt;
&lt;p&gt;Although between the two approaches mentioned above, I would recommend going with the
&lt;code&gt;beangulp&lt;&#x2F;code&gt; approach.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;2-fava-compatibility&quot;&gt;2. Fava compatibility&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Update (May 2025)&lt;&#x2F;strong&gt;: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;beancount&#x2F;fava&#x2F;issues&#x2F;1860#issuecomment-2564703652&quot;&gt;Fava does support Beancount 3.x&lt;&#x2F;a&gt;!&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;del&gt;This one is unfortunate. It seems like Fava isn&#x27;t compatible with Beancount 3.x, yet.
There are a couple of Github issues (eg. &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;beancount&#x2F;fava&#x2F;issues&#x2F;1824&quot;&gt;#1824&lt;&#x2F;a&gt; and &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;beancount&#x2F;fava&#x2F;issues&#x2F;1831&quot;&gt;#1831&lt;&#x2F;a&gt;) that are open at the time
of this writing. So far I haven&#x27;t seen much activity on those issues, so I&#x27;m not sure
what the current state is.&lt;&#x2F;del&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;del&gt;If you&#x27;re upgrading to Beancount 3.x, be aware that you&#x27;ll lose out on Fava.&lt;&#x2F;del&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;3-adjusting-importers&quot;&gt;3. Adjusting Importers&lt;&#x2F;h2&gt;
&lt;p&gt;The next thing I noticed is that the importer class definitions need to be adjusted
because of API changes.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;beancount.ingest&lt;&#x2F;code&gt; package is gone and has been replaced with the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pypi.org&#x2F;project&#x2F;beangulp&#x2F;&quot;&gt;beangulp&lt;&#x2F;a&gt; module
which contains the new importer base (abstract) class and a few other utilities to work
with external data. The changes are not many and are not very big either. Here&#x27;s a quick
list:&lt;&#x2F;p&gt;
&lt;h3 id=&quot;3-1-updated-base-importer-class&quot;&gt;3.1. Updated base importer class&lt;&#x2F;h3&gt;
&lt;p&gt;The first change is, of course, that &lt;code&gt;beancount.ingest&lt;&#x2F;code&gt; is gone. Well, not completely
gone, but at least gone from &lt;code&gt;beancount&lt;&#x2F;code&gt;. This functionality has now been extracted to
the &lt;code&gt;beangulp&lt;&#x2F;code&gt; module.&lt;&#x2F;p&gt;
&lt;p&gt;In Beancount 2.x the importers were inheriting from
&lt;code&gt;beancount.ingest.importer.ImporterProtocol&lt;&#x2F;code&gt;. In 3.x, they should now inherit from
&lt;code&gt;beangulp.importer.Importer&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; beangulp.importer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; Importer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; MyImporter&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Importer&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;3-2-updated-file-pointer-type-in-method-signatures&quot;&gt;3.2. Updated file pointer type in method signatures&lt;&#x2F;h3&gt;
&lt;p&gt;The second change is in the method signatures of methods like &lt;code&gt;extract&lt;&#x2F;code&gt; or &lt;code&gt;identify&lt;&#x2F;code&gt;.
In &lt;code&gt;beancount.ingest.importer.ImporterProtocol&lt;&#x2F;code&gt;, these methods used to accept a
file-like object (&lt;code&gt;cache._FileMemo&lt;&#x2F;code&gt;) as a parameter.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; extract&lt;&#x2F;span&gt;&lt;span&gt;(self, file: cache._FileMemo):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    pass&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This file-like object has been replaced with a string filepath, which is a bit more
straightforward to work with.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; extract&lt;&#x2F;span&gt;&lt;span&gt;(self, filepath:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; str&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    pass&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;3-3-importers-don-t-have-self-flag-anymore&quot;&gt;3.3. Importers don&#x27;t have &lt;code&gt;self.FLAG&lt;&#x2F;code&gt; anymore&lt;&#x2F;h3&gt;
&lt;p&gt;This is a minor one, but I think it might be worth mentioning. If your importers are
using &lt;code&gt;self.FLAG&lt;&#x2F;code&gt; in any of the methods, it won&#x27;t work anymore. You&#x27;ll need to replace
it with one of the flags defined in &lt;code&gt;beancount.core.flags&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;3-4-method-names&quot;&gt;3.4. Method names&lt;&#x2F;h3&gt;
&lt;p&gt;And the final change I noticed is how the importer method names have changed.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;beangulp.importer.Importer&lt;&#x2F;code&gt; defines a new interface that importers should implement.
The &lt;code&gt;identify&lt;&#x2F;code&gt; and &lt;code&gt;extract&lt;&#x2F;code&gt; methods have been kept. Other than that, the
&lt;code&gt;file_account&lt;&#x2F;code&gt;, &lt;code&gt;file_date&lt;&#x2F;code&gt;, and &lt;code&gt;file_name&lt;&#x2F;code&gt; methods have been renamed to remove the
&lt;code&gt;file_&lt;&#x2F;code&gt; prefix. So &lt;code&gt;file_account&lt;&#x2F;code&gt; has become &lt;code&gt;account, file_date&lt;&#x2F;code&gt; has become &lt;code&gt;date&lt;&#x2F;code&gt;, and
&lt;code&gt;file_name&lt;&#x2F;code&gt; has become &lt;code&gt;filename&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The class is defined &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;beancount&#x2F;beangulp&#x2F;blob&#x2F;master&#x2F;beangulp&#x2F;importer.py&quot;&gt;here&lt;&#x2F;a&gt;, in case you&#x27;d like to see the complete interface yourself.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;And that&#x27;s pretty much it. After making the above changes to your importers and your
workflow, your setup &lt;em&gt;should&lt;&#x2F;em&gt; be compatible with Beancount 3.x. As I mentioned earlier,
the list of changes is not very big. And the changes themselves aren&#x27;t that big either.
However, I couldn&#x27;t find a migration document that would guide me through the process,
so I thought about writing a quick one up myself.&lt;&#x2F;p&gt;
&lt;p&gt;I hope this blog post was helpful. If you have any questions (or noticed something in
this blog post that is not correct), please feel free to reach out to me on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;bsky.app&#x2F;profile&#x2F;sgoel.dev&quot;&gt;Bluesky&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
&lt;p&gt;(Oh and btw, if you&#x27;d like to learn more about how you can track your personal finances
using Python and Beancount and don&#x27;t mind sticking with Beancount 2.x for now, I &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;personalfinancespython.com&quot;&gt;wrote
a book&lt;&#x2F;a&gt; on the topic. 🙃)&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Handing over the reins of GeniePy</title>
        <published>2024-06-25T00:00:00+00:00</published>
        <updated>2024-06-25T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/handing-over-the-reins-of-geniepy/"/>
        <id>https://sgoel.dev/posts/handing-over-the-reins-of-geniepy/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/handing-over-the-reins-of-geniepy/">&lt;p&gt;A few days ago, I finished the process of handing over the maintenance and further
development of &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;geniepy.com&quot;&gt;GeniePy&lt;&#x2F;a&gt; to a new owner. I&#x27;ve worked on the project for around 3 years
and after all this time, it was finally time to sell. In this blog post I&#x27;ll briefly
describe the process.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;starlette-beginnings&quot;&gt;Starlette beginnings&lt;&#x2F;h2&gt;
&lt;p&gt;I started building GeniePy sometime back in 2021. At that time I had a lot of SaaS
business ideas but the first step towards building any of those was to build the basic
SaaS functions first - user registration, login, payments, password resets, the list
goes on.&lt;&#x2F;p&gt;
&lt;p&gt;So I built GeniePy mostly for myself. The idea of building and selling a SaaS
boilerplate in Python wasn&#x27;t entirely new. Indeed, a few SaaS boilerplates in Python
already existed on the market. What was different was the technology stack. Every
engineer has a slightly different taste in terms of tech stack, and mine at that time
involved &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;encode&#x2F;starlette&quot;&gt;Starlette&lt;&#x2F;a&gt;. So I wrote the first version of GeniePy on top of Starlette.&lt;&#x2F;p&gt;
&lt;p&gt;Over time, I realized that even though Starlette was (and continues to be) an excellent
framework, it wasn&#x27;t very well known in the startup world. &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tiangolo&#x2F;fastapi&quot;&gt;FastAPI&lt;&#x2F;a&gt;, which is an API
framework built on top of Starlette, was (and is) extremely popular, but unfortunately
Starlette wasn&#x27;t. This was a problem. Making online sales is already pretty difficult.
And if the product has an inherent quality that can potentially act as a blocker in
making sales, that&#x27;s not a very good situation to be in.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-rewrite-in-reflex&quot;&gt;The rewrite in Reflex&lt;&#x2F;h2&gt;
&lt;p&gt;Around mid 2022, the Python ecosystem saw the launch of a framework called &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;reflex.dev&quot;&gt;Reflex&lt;&#x2F;a&gt;.
Reflex came with the promise that you could code your &lt;em&gt;entire&lt;&#x2F;em&gt; application (backend and
frontend) in Python. All you had to do was stick to the data structures and primitives
that Reflex offered. And at deployment time, your code would be compiled into a FastAPI
backend and a Next.js frontend, all taken care of by Reflex. You could deploy the
FastAPI server as you would deploy any other backend API. And the Next.js frontend could
be deployed as a static site (or as any other Node application).&lt;&#x2F;p&gt;
&lt;p&gt;This sounded very exciting to me. After the realization that I would have to move away
from Starlette, I was looking for other frameworks to rewrite the boilerplate in. Reflex
seemed like an excellent fit. At that time I was between jobs, so I decided to just go
for it. It took me about 2-3 weeks to finish the rewrite (in whatever time I had left
over from job interviews). I was even able to find time to take part in the Reflex
community. I joined their Discord, managed to &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;reflex-dev&#x2F;reflex&#x2F;pulls&#x2F;siddhantgoel&quot;&gt;submit a few PRs&lt;&#x2F;a&gt; to the Reflex
framework, and even helped them test out the beta for their &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;reflex.dev&#x2F;docs&#x2F;hosting&#x2F;deploy-quick-start&#x2F;&quot;&gt;hosting service&lt;&#x2F;a&gt;. The
Reflex community was very welcoming and friendly to this newcomer.&lt;&#x2F;p&gt;
&lt;p&gt;After the rewrite was complete, I &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;geniepy.com&#x2F;blog&#x2F;switching-from-starlette-to-reflex&#x2F;&quot;&gt;launched the updated version&lt;&#x2F;a&gt;. That launch felt very
good. It brought a lot of new energy to the project. And it felt like the updated
version would be very useful to Python developers looking to build and ship SaaS
applications quickly. I wrote a few blog posts, posted the links to a few online water
coolers, and managed to get some more attention to the project, some of which turned
into customers.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;time&quot;&gt;Time&lt;&#x2F;h2&gt;
&lt;p&gt;After the rewrite to Reflex and the overall positive response, I started to have users
ask me for more features.&lt;&#x2F;p&gt;
&lt;p&gt;I managed to ship quite a few of those. But there were a few others which were excellent
feature suggestions but a bit too heavy of a lift to be done within the spare time I
actually had. At around the same time, a few changes happened in my personal life which
resulted in me having even less time (and energy) available to devote to side projects.
GeniePy was still active and I was shipping security updates, but new feature
development had effectively paused.&lt;&#x2F;p&gt;
&lt;p&gt;This was the time that I realized that it might be time to move on. I had been working
on the project for close to three years. If I wasn&#x27;t able to find the time and energy to
work on it, I wanted to find someone else who did.&lt;&#x2F;p&gt;
&lt;p&gt;So, I started looking for potential individuals (or companies) willing to take the
project on and invest more time and resources into it.&lt;&#x2F;p&gt;
&lt;p&gt;Luckily, with the rise of the Indie Hacker movement, the process of selling businesses
online has become a lot more convenient than it used to be. I posted the project on a
few micro-SaaS acquisition marketplaces and received quite a few leads.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;buy-sell-startups&quot;&gt;Buy &#x2F;&#x2F; Sell Startups&lt;&#x2F;h2&gt;
&lt;p&gt;A few weeks after posting the project on micro-SaaS marketplaces, I received an email
through &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;buysellstartups.com&quot;&gt;Buy &#x2F;&#x2F; Sell Startups&lt;&#x2F;a&gt; from someone interested in acquiring GeniePy. This was
from Hannes Lehmann running the companies &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;sistemica.de&quot;&gt;sistemica&lt;&#x2F;a&gt; and &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;devtech.pro&quot;&gt;devtech.pro&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;We exchanged a few emails and had an initial chat on Google Hangouts where we first got
to know each other, and then spoke about the project. We talked about the technology
stack, the revenue GeniePy was making, the future development ideas I had, Hannes&#x27;s
plans for it, and so on.&lt;&#x2F;p&gt;
&lt;p&gt;The first call went really well. I had the feeling that Hannes really saw the potential
in the project and was willing to invest the time and energy into improving it further.&lt;&#x2F;p&gt;
&lt;p&gt;After the initial chat, we scheduled a few more follow-up calls, where I did codebase
walkthroughs, explained the different workflows I had set up to publish releases,
basically everything that went behind the scenes. After those follow-up calls, we let
everything settle in our brains for a few days, allowing both of us to process all that
new information.&lt;&#x2F;p&gt;
&lt;p&gt;And after that delay, we both felt good about making the transfer. It generally felt
like a very good mutual fit. I personally was very happy that the project could again
get the time and energy it really deserved. We mutually decided upon June 1st as the
date to transfer all the accounts from me to Hannes.&lt;&#x2F;p&gt;
&lt;p&gt;The transfer itself went quite smooth. Coincidentally, Hannes was (is) also based in
Germany, which made the whole thing a lot more convenient because we were both familiar
with conducting business in this country. Honestly, the longest part of the process was
just the DNS delay as a result of moving from one provider to another.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;Overall, I&#x27;m super happy about GeniePy having a new owner.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s been only about a month and GeniePy has already been upgraded to support Reflex
5.0, which was something that the users had started asking for recently. The &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;geniepy.com&#x2F;blog&#x2F;news-development-lead-and-vision&#x2F;&quot;&gt;future
plans documented&lt;&#x2F;a&gt; by the new team also look very much aligned with what the user
feedback over the last few months has been.&lt;&#x2F;p&gt;
&lt;p&gt;I think it&#x27;s safe to say that the future for GeniePy looks quite bright.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Experiences using Tailwind CSS</title>
        <published>2024-05-20T00:00:00+00:00</published>
        <updated>2024-05-20T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/experiences-using-tailwind-css/"/>
        <id>https://sgoel.dev/posts/experiences-using-tailwind-css/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/experiences-using-tailwind-css/">&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;tailwindcss.com&quot;&gt;Tailwind CSS&lt;&#x2F;a&gt; has gained a lot of popularity in the recent years, for many good
reasons. There are a ton of interesting things that it brings to the table.&lt;&#x2F;p&gt;
&lt;p&gt;One thing that &lt;em&gt;I&lt;&#x2F;em&gt; personally found interesting is that a lot of that popularity seems
to come from backend developers, who almost by definition, don&#x27;t write a lot of code on
the frontend. As someone who also subscribes to that definition (at least to some
extent), I found that bit quite intriguing. Developing code that&#x27;s visible to the
end-user has always been a bit of an Achilles&#x27; heel for me. So the thought of solving
that problem was honestly quite exciting.&lt;&#x2F;p&gt;
&lt;p&gt;So, over the last few evenings and weekends, I rewrote the styling for this site using
Tailwind, mostly to figure out what I can do with it. This post describes my experience
with Tailwind so far.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;background&quot;&gt;Background&lt;&#x2F;h2&gt;
&lt;p&gt;Before we begin, it would help to establish a little bit of context. As I wrote earlier,
my core technical competency is backend and infrastructure&#x2F;devops software development.
I &lt;em&gt;have&lt;&#x2F;em&gt; written frontend code in the past, but it was not where I spent &lt;em&gt;most&lt;&#x2F;em&gt; of my
time. In the past whenever I had to kick-start a project, my first thought was to use
either &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;getbootstrap.com&quot;&gt;Bootstrap&lt;&#x2F;a&gt; or &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;bulma.io&quot;&gt;Bulma&lt;&#x2F;a&gt;. More often than not, the final result looked as if it was
&quot;engineered&quot; and not &quot;designed&quot; (which was not the fault of the framework but rather a
consequence of my limited design skills). It wouldn&#x27;t be an exaggeration to say that I
could identify with the following gif.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;sgoel.dev&#x2F;posts&#x2F;experiences-using-tailwind-css&#x2F;css.gif&quot; alt=&quot;Backend Developer using CSS&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Usually when I want to learn something new I like to read a book or a few blog posts to
get up to speed. In this case I chose to dive heads first. I picked my personal website
(the thing you&#x27;re reading right now) as a playground and decided to rewrite the styling
using Tailwind.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;making-tailwind-work-with-zola&quot;&gt;Making Tailwind work with Zola&lt;&#x2F;h2&gt;
&lt;p&gt;This site is built using &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.getzola.org&quot;&gt;Zola&lt;&#x2F;a&gt; and deployed on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.netlify.com&quot;&gt;Netlify&lt;&#x2F;a&gt; as a static site. I don&#x27;t use
a third-party theme, which means that all the HTML code on this site is under my
control.&lt;&#x2F;p&gt;
&lt;p&gt;The first step was to install Tailwind. I ran &lt;code&gt;npm install tailwindcss&lt;&#x2F;code&gt; on the console
and had that out of the way.&lt;&#x2F;p&gt;
&lt;p&gt;The next step was to initialize Tailwind, which is done using &lt;code&gt;npx tailwindcss init&lt;&#x2F;code&gt;.
Running this command writes a &lt;code&gt;tailwind.config.js&lt;&#x2F;code&gt; file in the project root. I had to
adjust the &lt;code&gt;content&lt;&#x2F;code&gt; key to point to the directory where my Zola template files live, so
that the Tailwind CLI can look at what CSS classes the templates are using, and
therefore should be included in the final CSS file served to the browser. I also had to
install the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tailwindlabs&#x2F;tailwindcss-typography&quot;&gt;&lt;code&gt;@tailwindcss&#x2F;typography&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; plugin to make sure that the Markdown content
which is rendered to HTML (by Zola) has good typographic defaults. The final
&lt;code&gt;tailwind.config.js&lt;&#x2F;code&gt; looks like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;**&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; @type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; {import(&amp;#39;tailwindcss&amp;#39;).Config}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt; *&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;module&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;exports&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    content: [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;.&#x2F;templates&#x2F;**&#x2F;*.html&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    theme: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        extend: {},&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    plugins: [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;        require&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;@tailwindcss&#x2F;typography&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Next, I created a &lt;code&gt;main.css&lt;&#x2F;code&gt; file in the project root with the following contents:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;css&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;@tailwind&lt;&#x2F;span&gt;&lt;span&gt; base;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;@tailwind&lt;&#x2F;span&gt;&lt;span&gt; components;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;@tailwind&lt;&#x2F;span&gt;&lt;span&gt; utilities;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And then I ran the command &lt;code&gt;tailwindcss -i .&#x2F;main.css -o .&#x2F;static&#x2F;main.css --watch --minify&lt;&#x2F;code&gt;, and let it run in a separate terminal window. This command watches &lt;code&gt;main.css&lt;&#x2F;code&gt;
and all the &lt;code&gt;*.html&lt;&#x2F;code&gt; template files for changes and recompiles (and minifies) the final
CSS file to be included in the source code. The output is written to &lt;code&gt;static&#x2F;main.css&lt;&#x2F;code&gt;
which is served as the final result using a &lt;code&gt;&amp;lt;link&amp;gt;&lt;&#x2F;code&gt; tag.&lt;&#x2F;p&gt;
&lt;p&gt;For simplicity, I checked &lt;code&gt;static&#x2F;main.css&lt;&#x2F;code&gt; into version control. The alternative would
have been to generate it at build time in Netlify but that would have introduced
additional complexity which I didn&#x27;t want.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-i-like&quot;&gt;What I like&lt;&#x2F;h2&gt;
&lt;p&gt;Overall, there is a &lt;em&gt;lot&lt;&#x2F;em&gt; to like about Tailwind. My experience has been so positive
that I see myself reaching out for Tailwind before any other CSS framework in the near
future. In this section I&#x27;ll write about the two things that jumped out the most.&lt;&#x2F;p&gt;
&lt;p&gt;The first was how quick and easy Tailwind made it for me to get things in to a working
state. Adjusting things using CSS has always been an issue for me. Frameworks like
Bootstrap helped, but the moment something was out of scope of the framework (eg.
modifying the appearance when the cursor is hovering over an element), I had to resort
to searching on Google and copy&#x2F;pasting code without having much of an understanding of
what it was doing.&lt;&#x2F;p&gt;
&lt;p&gt;With Tailwind, I can set things in place &lt;em&gt;really&lt;&#x2F;em&gt; quickly. What helps me is to think of
a page as a collection of components that are mostly independent of each other. This
way, it&#x27;s possible to define them individually, including thinking about how they should
look like in responsive contexts. The &quot;Flexbox &amp;amp; Grid&quot; utility classes are especially
helpful here.&lt;&#x2F;p&gt;
&lt;p&gt;The second thing that jumped out to me is how easy it is to make things look nice. I
know that &quot;nice&quot; is a subjective word; maybe non-ugly is a better word? In any case, my
experience has been that Tailwind generally makes things look nice by default. &lt;em&gt;And&lt;&#x2F;em&gt;,
there is a vast collection of utility classes that lets you control the look and feel of
essentially everything on the page. It&#x27;s easy to control spacing using margins and
paddings, control how text looks like, what colors are used (both for the main content
and the background), transitions, and so much more. The number of utility classes is
really enormous and I think I&#x27;ve barely scratched the surface so far. But my impression
so far is that if you can think it, you can build it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-i-don-t-like&quot;&gt;What I don&#x27;t like&lt;&#x2F;h2&gt;
&lt;p&gt;There&#x27;s actually not much that I don&#x27;t like. Regardless, to balance things out, I tried
hard to think about it and was able to come up with two.&lt;&#x2F;p&gt;
&lt;p&gt;The first one is that the HTML code is not as clean as I would like. It&#x27;s OK for a
personal website like this where I&#x27;d like to keep the appearance simple. However, as the
design gets more intricate, the number of CSS classes also explodes. Tailwind authors
suggest using partials or components to solve this problem. I can absolutely agree. I&#x27;d
probably even go one step further and say that without partials (or components) it&#x27;s
quickly going to go out of hand. So if you&#x27;re using Tailwind, keep it in mind that at
some point, you&#x27;ll have to create those abstractions.&lt;&#x2F;p&gt;
&lt;p&gt;The second thing that I don&#x27;t &lt;del&gt;like&lt;&#x2F;del&gt; know what to think of yet is the culture of
copy&#x2F;paste components that has cropped up in the ecosystem. There are a lot of Tailwind
UI component libraries out there, &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;tailwindui.com&quot;&gt;Tailwind UI&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;preline.co&quot;&gt;Preline&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;flowbite.com&quot;&gt;Flowbite&lt;&#x2F;a&gt; being the most
popular ones. The usage instructions for all these libraries is &quot;here is the HTML code,
copy and paste it in to your project&quot;. In one of the side projects I&#x27;ve been using
Tailwind UI components. The components themselves are extremely well done, which also
means that the number of CSS classes is high. That in turn means that if you don&#x27;t make
some time upfront to go through the code and understand what those classes are doing,
you might end up missing context. Traditionally, when you use a library, the library
puts an abstraction between you and the underlying code (eg. &lt;code&gt;btn&lt;&#x2F;code&gt; instead of &lt;code&gt;rounded bg-indigo-600 px-2 py-1 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600&lt;&#x2F;code&gt;). Without any such abstraction, you just have to
invest some time upfront to understand what each of those 13 classes are there for.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;Overall, I&#x27;ve come to really like Tailwind. In my experience, it&#x27;s an incredibly
powerful tool that allows someone like me (with a technical skillset that has
traditionally not focused on the frontend) to quickly build user-facing websites. This
lets me work on the entire stack instead of being limited to just the backend. It&#x27;s also
honestly a lot of fun to work with. I &lt;strong&gt;love&lt;&#x2F;strong&gt; the instant feedback of applying a
utility class and see the appearance change instantly. I can definitely see myself using
it more and more in the future.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>One of the most useful directories on my laptop</title>
        <published>2024-03-29T00:00:00+00:00</published>
        <updated>2024-03-29T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/one-of-the-most-useful-directories-on-my-laptop/"/>
        <id>https://sgoel.dev/posts/one-of-the-most-useful-directories-on-my-laptop/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/one-of-the-most-useful-directories-on-my-laptop/">&lt;p&gt;One of the most useful directories on my laptop is called &lt;code&gt;finances&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Almost every single time I touch it, I&#x27;m reminded of its usefulness. Every single year
when we have to file our tax returns, I&#x27;m again reminded of how useful it is. What does
it contain, one might ask? It contains a log of all my financial transactions across
different bank accounts (both active and inactive), in a plain-text format, dating all
the way back to the beginning of 2016. At the time of this writing, this is 8 years of
financial activity, in one directory (and it&#x27;s even version controlled!).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;background&quot;&gt;Background&lt;&#x2F;h3&gt;
&lt;p&gt;Before I talk about the cool stuff, let me quickly talk about Beancount.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;beancount.github.io&quot;&gt;Beancount&lt;&#x2F;a&gt; is one of the most useful programs (at least to me) in existence. It&#x27;s an
implementation of the double-entry bookkeeping system in Python. It lets you represent
financial transactions - money moving from one account to another - in an easy-to-read
plain text file. The file is easy to read for both humans and computers. Here&#x27;s an
example snippet:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;beancount&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2024&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;04&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01 *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Thank you for your purchase.&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Assets&lt;&#x2F;span&gt;&lt;span&gt;:MyBank&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;              -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;10.00&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Expenses&lt;&#x2F;span&gt;&lt;span&gt;:Supermarket&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        10.00&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;These three lines make up a transaction that conveys that an amount of €10 moved from
your bank account to your local supermarket&#x27;s account, perhaps when you went grocery
shopping.&lt;&#x2F;p&gt;
&lt;p&gt;Beancount files are a collection of lots and lots of such transactions. The way
everything works is that you download an export of your financial activity from your
bank (usually a CSV export), run a Python program that converts the CSV to the
&lt;code&gt;.beancount&lt;&#x2F;code&gt; format, and then append everything to your own &lt;code&gt;.beancount&lt;&#x2F;code&gt; file.&lt;&#x2F;p&gt;
&lt;p&gt;Over time, this file starts accumulating more and more of your financial activity and
starts to grow into a documentation of essentially how your money has been moving
between different accounts. This makes answering questions about your financial activity
almost trivial. For instance, if you want to know how much money your side-hustle (let&#x27;s
call it Apple) made back in 2018, all you have to do is filter for transactions that
deposit money into the &lt;code&gt;Income:Hustles:Apple&lt;&#x2F;code&gt; account (made-up name) and limit the
timeframe to 2018.&lt;&#x2F;p&gt;
&lt;p&gt;The concept is quite simple but it unlocks so many cool use cases. I&#x27;ll describe one
here.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;efficiently-preparing-tax-returns&quot;&gt;Efficiently preparing tax returns&lt;&#x2F;h3&gt;
&lt;p&gt;At the end of the year you normally have to file tax returns. At least in the country
where I live, this involves collecting lots and lots of receipts and invoices that you
can send to the tax authorities as proof that your business incurred some expenses, so
you can claim some of that taxed money back.&lt;&#x2F;p&gt;
&lt;p&gt;In my pre-Beancount days, this was the part I never liked. I never knew where to put
receipts. How was I supposed to find and collect them? I had a &lt;code&gt;Documents&lt;&#x2F;code&gt; folder on my
machine. Maybe that&#x27;s where the receipts should go? Should this folder be organized by
year, or by purpose, or by something else entirely? Also, what if I decide one
organizational scheme one day but then after a few months I find out that that was
completely stupid and I should be using a different organizational scheme? Does this
mean I have to rearrange everything?&lt;&#x2F;p&gt;
&lt;p&gt;Beancount lets me solve this problem very elegantly. You see, Beancount lets me attach
documents to transactions. Again, this is such a simple concept, but it is so powerful.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s illustrate using the following transaction.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;beancount&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2024&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;04&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01 *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Thank you for your purchase.&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Assets&lt;&#x2F;span&gt;&lt;span&gt;:MyBank&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;              -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;12.99&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Expenses&lt;&#x2F;span&gt;&lt;span&gt;:Netflix&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;            12.99&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Assume that this transaction lives inside &lt;code&gt;finances&#x2F;example.beancount&lt;&#x2F;code&gt;. If you have a
receipt from Netflix for this payment (and for whatever funky reason you want to show
this as a &lt;em&gt;business&lt;&#x2F;em&gt; expense), you can create a &lt;code&gt;finances&#x2F;documents&#x2F;&lt;&#x2F;code&gt; directory and
place the receipt at &lt;code&gt;finances&#x2F;documents&#x2F;Expenses&#x2F;Netflix&#x2F;2024-04-01.pdf&lt;&#x2F;code&gt;, and that&#x27;s
it! The &lt;code&gt;Expenses&#x2F;Netflix&#x2F;&lt;&#x2F;code&gt; directory tree represents the name of the account and the
file name is the same as the date of the transaction. These two things make sure that
that document is linked to this specific transaction.&lt;&#x2F;p&gt;
&lt;p&gt;Repeat this process with all the receipts and invoices you have and suddenly you
have a system of organizing (most of) your financial documents next to your financial
data. And it&#x27;s even version-controlled!&lt;&#x2F;p&gt;
&lt;p&gt;So when your tax advisor asks you for all those documents to file your tax returns,
it&#x27;ll take you less than 5 minutes to collect everything. Because all you would have to
do is go to the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;beancount.github.io&#x2F;fava&#x2F;&quot;&gt;Fava&lt;&#x2F;a&gt; web interface, set the time period to last year, and you&#x27;ll have
a list of all the relevant transactions with their documents attached. Download
everything and zip it off to your tax advisor. They&#x27;ll be so proud. 🫶🏻&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;That&#x27;s it! That was the entire post, me nerding out about something I find cool.&lt;&#x2F;p&gt;
&lt;p&gt;If you found this post interesting and also like to nerd out about keeping track of
personal finances and about organizing things, I wrote &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;personalfinancespython.com&quot;&gt;an entire book on this topic&lt;&#x2F;a&gt;
that you might find interesting.&lt;&#x2F;p&gt;
&lt;p&gt;The book assumes that you are comfortable with reading and writing Python code, and
teaches you how you can build your own system to keep track of your personal finances in
a developer-friendly manner using only open-source software. Readers have given it good
feedback so far, so if you&#x27;re into such nerdy topics, I think you would like it too! 🙃&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>LangChain applications using Ollama</title>
        <published>2023-11-19T00:00:00+00:00</published>
        <updated>2023-11-19T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/langchain-applications-using-ollama/"/>
        <id>https://sgoel.dev/posts/langchain-applications-using-ollama/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/langchain-applications-using-ollama/">&lt;p&gt;Like almost everyone else in the tech industry today, I&#x27;ve been looking into LLMs
recently. I&#x27;m still fairly new to everything but I feel like I&#x27;m starting to get a
general sense of how things work. What&#x27;s &lt;em&gt;inside&lt;&#x2F;em&gt; an LLM is something I&#x27;m (at least at
this point) still considering a black box. Regardless, it&#x27;s fun to play around with
these models and learn more about how they work and how software developers can start
using these in their toolkits.&lt;&#x2F;p&gt;
&lt;p&gt;As part of this, I tried to write a simple &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.langchain.com&#x2F;&quot;&gt;LangChain&lt;&#x2F;a&gt; program that lets me
programmatically work with an LLM running on my machine. The code examples shown on the
LangChain documentation default to using OpenAI&#x27;s ChatGPT, but for whatever reason my
OpenAI API keys keep getting rate limited. I didn&#x27;t feel like shelling out $20 yet, and
besides, I &lt;em&gt;have&lt;&#x2F;em&gt; an LLM running locally, so why not make use of it.&lt;&#x2F;p&gt;
&lt;p&gt;Fortunately, LangChain &lt;em&gt;can&lt;&#x2F;em&gt; work with Ollama. The following sections describe the steps
I took to get everything working.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;1-install-ollama&quot;&gt;1. Install Ollama&lt;&#x2F;h3&gt;
&lt;p&gt;The first thing to do is, of course, have an LLM running locally! We&#x27;ll use &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ollama.ai&quot;&gt;Ollama&lt;&#x2F;a&gt; to
do this. On macOS, the easiest way is to use &lt;code&gt;brew install ollama&lt;&#x2F;code&gt; to install Ollama and
&lt;code&gt;brew services&lt;&#x2F;code&gt; to keep it running.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;~&#x2F;W&#x2F;l&#x2F;llms main ❯ brew services start ollama &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;==&amp;gt; Successfully started `ollama` (label: homebrew.mxcl.ollama)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;At this point Ollama should be listening on port 11434 for incoming requests. You can
open up &lt;code&gt;http:&#x2F;&#x2F;localhost:11434&#x2F;&lt;&#x2F;code&gt; in the browser to double check.&lt;&#x2F;p&gt;
&lt;p&gt;Next, browse through the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ollama.ai&#x2F;library&quot;&gt;Ollama library&lt;&#x2F;a&gt; and choose which model you want to run
locally. In this case we want to run &lt;code&gt;llama2&lt;&#x2F;code&gt; so let&#x27;s ask Ollama to make that happen.
Run &lt;code&gt;ollama pull llama2&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;2-install-langchain&quot;&gt;2. Install LangChain&lt;&#x2F;h3&gt;
&lt;p&gt;The next step is to have a Python project with all the necessary dependencies installed.&lt;&#x2F;p&gt;
&lt;p&gt;Initialize a Python project somewhere on your machine, using whatever tools you use. I
personally use &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;python-poetry.org&#x2F;&quot;&gt;poetry&lt;&#x2F;a&gt; to manage Python projects. The following command should take
care of installing all the dependencies.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;~&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;W&#x2F;l&#x2F;llms main ❯ poetry add fastapi langchain langserve sse-starlette uvicorn&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;3-write-the-code&quot;&gt;3. Write the code&lt;&#x2F;h3&gt;
&lt;p&gt;Now that we have all the dependencies in place, let&#x27;s focus on the code! Here&#x27;s the code
I used:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; typing&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; List&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; fastapi&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; FastAPI&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; langchain.llms&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; Ollama&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; langchain.output_parsers&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; CommaSeparatedListOutputParser&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; langchain.prompts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; PromptTemplate&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; langserve&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; add_routes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; uvicorn&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;llama2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; Ollama(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;model&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;llama2&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;template&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; PromptTemplate.from_template(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;Tell me a joke about &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;{topic}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;.&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;chain&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; template&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span&gt; llama2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span&gt; CommaSeparatedListOutputParser()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;app&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; FastAPI(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;LangChain&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; version&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;1.0&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; description&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;The first server ever!&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;add_routes(app, chain,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; path&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&#x2F;chain&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; __name__&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;__main__&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    uvicorn.run(app,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; host&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;localhost&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; port&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;9001&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;As you can see, there are a few things going on in this code. We first build an &lt;code&gt;Ollama&lt;&#x2F;code&gt;
model object with the model set to llama2. Next we write a simple prompt template with a
parameter called &lt;code&gt;topic&lt;&#x2F;code&gt;. We&#x27;ll later see how the user can pass a topic to get back a
response from the LLM. Next, we initialize a &lt;code&gt;chain&lt;&#x2F;code&gt; using the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;python.langchain.com&#x2F;docs&#x2F;expression_language&#x2F;&quot;&gt;LangChain Expression
Language&lt;&#x2F;a&gt;. Finally, we initialize a new FastAPI application and then use
&lt;code&gt;langserve.add_routes&lt;&#x2F;code&gt; to mount the LangServe API routes.&lt;&#x2F;p&gt;
&lt;p&gt;Save this code to a file called &lt;code&gt;main.py&lt;&#x2F;code&gt; and run it using &lt;code&gt;python main.py&lt;&#x2F;code&gt;. This should
start a FastAPI server containing the LangServe endpoint we just defined. On my machine
I see the following message when the app starts:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;~&#x2F;W&#x2F;l&#x2F;llms main ❯ python chain.py&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;INFO:     Started server process [25442]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;INFO:     Waiting for application startup.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; __          ___      .__   __.   _______      _______. _______ .______     ____    ____  _______ &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|  |        &#x2F;   \     |  \ |  |  &#x2F;  _____|    &#x2F;       ||   ____||   _  \    \   \  &#x2F;   &#x2F; |   ____|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|  |       &#x2F;  ^  \    |   \|  | |  |  __     |   (----`|  |__   |  |_)  |    \   \&#x2F;   &#x2F;  |  |__   &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|  |      &#x2F;  &#x2F;_\  \   |  . `  | |  | |_ |     \   \    |   __|  |      &#x2F;      \      &#x2F;   |   __|  &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|  `----.&#x2F;  _____  \  |  |\   | |  |__| | .----)   |   |  |____ |  |\  \----.  \    &#x2F;    |  |____ &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|_______&#x2F;__&#x2F;     \__\ |__| \__|  \______| |_______&#x2F;    |_______|| _| `._____|   \__&#x2F;     |_______|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;LANGSERVE: Playground for chain &amp;quot;&#x2F;chain&#x2F;&amp;quot; is live at:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;LANGSERVE:  │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;LANGSERVE:  └──&amp;gt; &#x2F;chain&#x2F;playground&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;LANGSERVE:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;LANGSERVE: See all available routes at &#x2F;docs&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;LANGSERVE: ⚠️ Using pydantic 2.5.1. OpenAPI docs for invoke, batch, stream, stream_log endpoints will not be generated. API endpoints and playground should work as expected. If you need to see the docs, you can downgrade to pydantic 1. For example, `pip install pydantic==1.10.13`. See https:&#x2F;&#x2F;github.com&#x2F;tiangolo&#x2F;fastapi&#x2F;issues&#x2F;10360 for details.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;INFO:     Application startup complete.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;INFO:     Uvicorn running on http:&#x2F;&#x2F;localhost:9001 (Press CTRL+C to quit)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And that should be it! You can now visit &lt;code&gt;http:&#x2F;&#x2F;localhost:9001&#x2F;chain&#x2F;playground&#x2F;&lt;&#x2F;code&gt; to
play around with the LLM interface you just built! Here&#x27;s a screenshot of the LangServe
Playground I see on my machine:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;sgoel.dev&#x2F;posts&#x2F;langchain-applications-using-ollama&#x2F;langserve-playground.png&quot; alt=&quot;LangServe Playground&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;It works! And the cool thing is that there are no API keys or anything involved. All the
code necessary for this application is running locally.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Switching between projects in Neovim</title>
        <published>2023-10-08T00:00:00+00:00</published>
        <updated>2023-10-08T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/switching-between-projects-in-neovim/"/>
        <id>https://sgoel.dev/posts/switching-between-projects-in-neovim/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/switching-between-projects-in-neovim/">&lt;p&gt;I&#x27;ve historically used &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;qvacua&#x2F;vimr&quot;&gt;Vimr&lt;&#x2F;a&gt; as my Neovim GUI. It&#x27;s a pretty nice macOS application
written in Swift that lets me open up multiple projects in separate windows and allows
me to switch between those windows using Cmd-`.&lt;&#x2F;p&gt;
&lt;p&gt;My usual development workflow consists of always having two applications open: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;sw.kovidgoyal.net&#x2F;kitty&#x2F;&quot;&gt;Kitty&lt;&#x2F;a&gt;
and Vimr. For each project, I have a terminal session open in a Kitty window and the
source code open in a Vimr window. I constantly switch back and forth between the
two using Cmd-Tab. And every now and then I need to switch between projects which is
where the Cmd-` shortcut was quite handy.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-problem&quot;&gt;The Problem&lt;&#x2F;h3&gt;
&lt;p&gt;A few months ago, one of the plugins I use (Telescope) started using some functionality
only available in Neovim 0.9+, while Vimr was based on 0.8.2. And it looked like the
Vimr project had &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;qvacua&#x2F;vimr&#x2F;issues&#x2F;1011&quot;&gt;gone inactive&lt;&#x2F;a&gt;, which meant that I couldn&#x27;t update my plugins without
breaking the whole thing. This in turn meant that the ability to switch between projects
using Cmd-` (which is an essential part of how I work) was gone.&lt;&#x2F;p&gt;
&lt;p&gt;So I could either wait for the Vimr maintainers to push an update, or find a different
way to switch between projects. I chose the second option. I replaced Vimr with
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;neovide.dev&#x2F;&quot;&gt;Neovide&lt;&#x2F;a&gt; and started looking for plugins that would let me switch between projects
within a single Neovim session easily, essentially replacing Cmd-`.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;workspaces&quot;&gt;Workspaces&lt;&#x2F;h3&gt;
&lt;p&gt;One of the plugins I found was &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;natecraddock&#x2F;workspaces.nvim&quot;&gt;workspaces.nvim&lt;&#x2F;a&gt; which lets you add, remove and switch
between workspaces within a single Neovim session. There&#x27;s also a Telescope integration
so I could set up a keyboard shortcut that would pop up a list of all the available
workspaces and let me switch between them easily.&lt;&#x2F;p&gt;
&lt;p&gt;This worked quite well. The only hiccup was that when I would switch between projects,
all the open buffers from the older project would remain while Neovim&#x27;s current working
directory would be set to the one from the new project. This meant that every workspace
switch involved manual work of closing the existing buffers and opening up new ones.&lt;&#x2F;p&gt;
&lt;p&gt;Luckily, the author of workspaces.nvim had written another plugin that helped me solve
this problem.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;sessions&quot;&gt;Sessions&lt;&#x2F;h3&gt;
&lt;p&gt;Vim&#x2F;Neovim has a concept of sessions which represent the current state of your work
(windows, buffers, etc.). The interface isn&#x27;t the easiest to use as the native commands
feel quite low level. But the author of workspaces.nvim wrote another plugin called
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;natecraddock&#x2F;sessions.nvim&quot;&gt;sessions.nvim&lt;&#x2F;a&gt; which provides a nice wrapper around the underlying interface.&lt;&#x2F;p&gt;
&lt;p&gt;After installing sessions.nvim, you can run &lt;code&gt;SessionsSave&lt;&#x2F;code&gt; and &lt;code&gt;SessionsLoad&lt;&#x2F;code&gt; to save
and load session files. So with these two commands in place, what remained was to figure
out how to hook up workspaces.nvim to save a session before a workspace switch and load
a session right after.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;workspaces-sessions&quot;&gt;Workspaces + Sessions&lt;&#x2F;h3&gt;
&lt;p&gt;Workspaces.nvim allows for &quot;hooks&quot; which are functions or commands you can run when
certain pre-defined events happen. In my case, what I wanted (roughly) was that when I
switch from one project to another, Neovim should:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;save the state of the first project to a session&lt;&#x2F;li&gt;
&lt;li&gt;close the existing buffers&lt;&#x2F;li&gt;
&lt;li&gt;switch the current working directory to that of the second project&lt;&#x2F;li&gt;
&lt;li&gt;refresh &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;nvim-tree&#x2F;nvim-tree.lua&quot;&gt;NvimTree&lt;&#x2F;a&gt; to show the second project&#x27;s file structure, and finally&lt;&#x2F;li&gt;
&lt;li&gt;load a previously-saved session for the second project into the current window&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;It might sound like a lot of work but actually it was quite easy to represent in code
using a combination of workspaces.nvim and sessions.nvim. The following snippet is what
I ended up using:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;lua&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;require&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;workspaces&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;setup&lt;&#x2F;span&gt;&lt;span&gt;({&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    hooks&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        open_pre&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;            &amp;quot;SessionsSave&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;            &amp;quot;silent %bdelete!&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        open&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;            &amp;quot;SessionsLoad&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;            &amp;quot;NvimTreeOpen&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;})&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This setup gives me exactly what I want. I had to give up Cmd-`, but I can now use
Ctrl-k to bring up a list of available workspaces and switch between them easily.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;open_pre&lt;&#x2F;code&gt; hook is run &lt;em&gt;before&lt;&#x2F;em&gt; a workspace switch, where we save the current state
to a session and close the currently open buffers. And the &lt;code&gt;open&lt;&#x2F;code&gt; hook is run &lt;em&gt;after&lt;&#x2F;em&gt; a
workspace switch, where we first load a previously-saved session and then update the
NvimTree panel. The complete code containing the &lt;code&gt;setup&lt;&#x2F;code&gt; functions of the other plugins
is available in my &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;dotfiles&#x2F;blob&#x2F;main&#x2F;nvim&#x2F;lua&#x2F;plugins.lua&quot;&gt;plugins.lua&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;So far I&#x27;m quite happy with this solution. I really like that both workspaces.nvim and
sessions.nvim provide a fairly minimal but functional API, particularly how
workspaces.nvim hooks let you bring other plugins into the picture and enable cool
things.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s quite a pity for Vimr to not be active anymore. It&#x27;s an excellent application and I
hope the author would be able to spare some time again in the near future to bring it
back. At the moment though, I&#x27;m quite happy with the switch to Neovide, and it&#x27;s a joy
to use the setup described in this post!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Experiences hunting for a software engineering job in 2023</title>
        <published>2023-06-21T00:00:00+00:00</published>
        <updated>2023-06-21T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/experiences-hunting-for-a-software-engineering-job-in-2023/"/>
        <id>https://sgoel.dev/posts/experiences-hunting-for-a-software-engineering-job-in-2023/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/experiences-hunting-for-a-software-engineering-job-in-2023/">&lt;p&gt;At the beginning of May 2023, Shopify &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;news.shopify.com&#x2F;important-team-and-business-changes&quot;&gt;decided to lay off&lt;&#x2F;a&gt; 20% of their
workforce. That 20% unfortunately also included me, which meant it was time for
me to look for another job.&lt;&#x2F;p&gt;
&lt;p&gt;So over the past ~6 weeks, I&#x27;ve been interviewing for senior&#x2F;staff backend
engineering roles almost full time. Here are some raw stats:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;In total I interviewed with about 30 companies.&lt;&#x2F;li&gt;
&lt;li&gt;I received a rejection from 13 of them.&lt;&#x2F;li&gt;
&lt;li&gt;About 10 companies I ended up declining myself.&lt;&#x2F;li&gt;
&lt;li&gt;5 of those were &quot;archived&quot;. Some of them ended up ghosting me, or they filled
up the position with someone else further along in the hiring process, or
there were internal restructurings in the company and the nature of the
position wasn&#x27;t clear anymore (which is something I found somewhat
interesting).&lt;&#x2F;li&gt;
&lt;li&gt;Last but not least, I accepted one of those offers.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;This blog post post is a collection of everything I observed and learned during
this process.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;1-there-is-less-hiring-overall&quot;&gt;1. There is less hiring overall.&lt;&#x2F;h3&gt;
&lt;p&gt;The first thing that jumped out to me almost right away is that the current job
market (at the time of this writing in June 2023) isn&#x27;t ideal.&lt;&#x2F;p&gt;
&lt;p&gt;Yes, there are certainly jobs out there. But it feels like there are less jobs
out there than, let&#x27;s say, 2 years ago. At that time, if you would visit the
careers page of almost any company, it was virtually guaranteed that they were
hiring software developers. At the time of this writing, not so much.&lt;&#x2F;p&gt;
&lt;p&gt;And if you&#x27;re like me and have specific criteria you&#x27;re looking to fulfill (eg.
in my case I was looking for fully remote or &lt;em&gt;maximum&lt;&#x2F;em&gt; one day in the office per
week, hopefully having something to do with the Python ecosystem, reasonable
salary, etc.) the list of potential companies reduces even further.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m hoping that the situation improves over the next few months. But at least as
of this writing I feel that the market is somewhat reluctant.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;2-startups-are-more-sober-when-it-comes-to-hiring&quot;&gt;2. Startups are more sober when it comes to hiring.&lt;&#x2F;h3&gt;
&lt;p&gt;... and that&#x27;s a good thing!&lt;&#x2F;p&gt;
&lt;p&gt;When interviewing with startups this time around, it&#x27;s interesting to hear lines
like &quot;we&#x27;re not in a rush to hire developers&quot;, which is in stark contrast to the
hiring wave 2-3 years ago.&lt;&#x2F;p&gt;
&lt;p&gt;Personally speaking, I&#x27;m 100% on board with this way of hiring. Adding
developers to the team just to prove to your investors that you&#x27;re growing never
felt sustainable in the first place. So now that the economic climate isn&#x27;t as
good, it seems like early-stage companies are focusing more on basic questions
like &quot;does our economic situation &#x2F; business model allow for hiring X
developers&quot; or &quot;how many developers do we actually need to build out X in to our
product&quot;, which I find rather nice.&lt;&#x2F;p&gt;
&lt;p&gt;As someone who is hunting for a software job, it definitely makes my life
harder. It&#x27;s still good to know that the overhiring trend is reversing.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;3-unreasonably-extensive-take-home-tests-are-normal&quot;&gt;3. Unreasonably extensive take-home tests are normal.&lt;&#x2F;h3&gt;
&lt;p&gt;This is something that bothered me the most.&lt;&#x2F;p&gt;
&lt;p&gt;One very common stage in interview processes is a &quot;take-home test&quot; or a &quot;coding
challenge&quot;. If you&#x27;re not familiar, the idea is that the company gives you a
programming problem to work on, a suggested timeframe, and a time limit, and you
have to submit your solution within that time limit. In my experience, these
problems are usually things like &quot;build a JSON API that exposes some business
logic over an endpoint&quot; or &quot;build a command line tool to solve X&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;The idea seems appealing, and if done right, it can be a nice way to filter
candidates out. Indeed, some of the companies I interviewed with had nice
take-home tests which left a good impression at the end.&lt;&#x2F;p&gt;
&lt;p&gt;The problem is that a lot of the companies seem to do it awfully wrong.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;3-1-the-tests-take-way-too-long&quot;&gt;3.1 The tests take way too long.&lt;&#x2F;h4&gt;
&lt;p&gt;Most of these tests are &lt;em&gt;way&lt;&#x2F;em&gt; too long. And it&#x27;s not always clear why. There
were times when it felt like the company just wanted me to jump through a 1-2
days long hoop without a specific objective in mind.&lt;&#x2F;p&gt;
&lt;p&gt;There are certainly cases when they&#x27;re upfront that they expect candidates to
spend 1-2 days preparing the solution. And then there are cases where the
instructions include something like &quot;please don&#x27;t spend more than a few hours
working on this&quot;. In reality though you &lt;em&gt;know&lt;&#x2F;em&gt; that there&#x27;s no way that that
particular problem can be conceptualized and solved in less than 2 days (if not
more) especially if you want to submit a solution that you personally would be
happy with.&lt;&#x2F;p&gt;
&lt;p&gt;I was interviewing full-time so I was able to spend that kind of time. I
honestly cannot imagine someone with a full-time job (and a life outside of that
job) to allocate that kind of time on such take-home tests.&lt;&#x2F;p&gt;
&lt;p&gt;Hiring managers: if you&#x27;re reading this, please please please make sure your
coding challenges can be conceptualized and implemented within 2-3 hours. You
want to assess whether the candidate can understand a problem and write good
code, not whether they can clone Facebook in 1 day.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;3-2-it-s-hard-to-predict-how-the-submission-will-be-evaluated&quot;&gt;3.2 It&#x27;s hard to predict how the submission will be evaluated.&lt;&#x2F;h4&gt;
&lt;p&gt;Another thing I noticed is that there&#x27;s a fair bit of likelihood for the
reviewers and you to &lt;em&gt;not&lt;&#x2F;em&gt; be on the same page regarding how the submission will
be evaluated.&lt;&#x2F;p&gt;
&lt;p&gt;Acceptance criteria is usually specified but either it&#x27;s vague or the feedback
that you receive (in case of a rejection) may make you feel that the reviewer
was expecting something different, which wasn&#x27;t clear in the original acceptance
criteria.&lt;&#x2F;p&gt;
&lt;p&gt;As an example, I personally find it very vague when a task description says &quot;the
solution should be production ready&quot;. In my mind, this raises so many more
follow-up questions. And even after clarifying those it may not be possible to
implement something to the reviewer&#x27;s liking. My take-away (after a few
rejections) is that in a lot of cases the interviewers are expecting you to
solve a problem the way &lt;em&gt;they&lt;&#x2F;em&gt; solve problems without explicitly communicating
it to you. How would that work? Your guess is as good as mine.&lt;&#x2F;p&gt;
&lt;p&gt;In another one of the tests I worked on, the task description mentioned &quot;please
don&#x27;t spend more than a few hours on this task&quot;. So I was honest and didn&#x27;t
spend more than a few hours working on the task. This meant I had to cut
corners. But I was aware of which corners I cut and we talked about those
corners in the follow-up interview.&lt;&#x2F;p&gt;
&lt;p&gt;After the follow-up interview I was given the feedback that the interviewers
felt I didn&#x27;t spend too much time on the task and hence thought I&#x27;m not very
interested in the job.&lt;&#x2F;p&gt;
&lt;p&gt;Wat?&lt;&#x2F;p&gt;
&lt;h3 id=&quot;4-linkedin-is-a-good-way-to-receive-leads&quot;&gt;4. LinkedIn is a good way to receive leads.&lt;&#x2F;h3&gt;
&lt;p&gt;One of the first things I did after being laid off was to update my LinkedIn
profile and set the status to &quot;open to work&quot;. I chose not to apply the publicly
visible (#opentowork) badge but instead make the status visible to only those in
recruiting positions (dedicated recruitment companies or otherwise).&lt;&#x2F;p&gt;
&lt;p&gt;Just doing that got me quite a few inbound requests that I probably might not
have run into otherwise.&lt;&#x2F;p&gt;
&lt;p&gt;In fact, the company I finally ended up signing with is one that reached out to
me on LinkedIn.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;5-there-is-more-competition-than-normal&quot;&gt;5. There is more competition than normal.&lt;&#x2F;h3&gt;
&lt;p&gt;As a consequence of multiple companies laying developers off one after the
other, there are simply more (very skilled) developers on the market than there
would normally be. This means that companies have too much of a choice. They&#x27;re
able to reject candidates knowing that there are plenty of other good ones in
the pipeline.&lt;&#x2F;p&gt;
&lt;p&gt;How do I know this?&lt;&#x2F;p&gt;
&lt;p&gt;There were a few companies where my interviews went &lt;em&gt;well&lt;&#x2F;em&gt;. The discussions were
productive. I was able to respond to their questions and tasks in a way that
they were happy with. And I didn&#x27;t notice any sign that would suggest that my
performance would warrant a rejection.&lt;&#x2F;p&gt;
&lt;p&gt;At the end I still received a rejection with the message that they decided to go
with another candidate who was better than me. 🤷🏻‍♂️&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;I&#x27;m not really sure how to conclude this blog post. Most of my learnings from
interviewing in this climate have not been too positive. So I can only hope that
this blog post didn&#x27;t come across as too much of a rant. 😅&lt;&#x2F;p&gt;
&lt;p&gt;In any case, if you&#x27;re a software developer currently in the middle of job
interviews and are frustrated or are close to it, just know that it has much
less to do with your skills as you might think. The job market is just not as
good at the moment and I hope that it improves over the next few months. Also,
acknowledging that job hunting and interviewing (particularly full-time) is
stressful can help.&lt;&#x2F;p&gt;
&lt;p&gt;Good luck!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>How to convert multiple images to PNG or JPEG on macOS</title>
        <published>2023-01-06T00:00:00+00:00</published>
        <updated>2023-01-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/how-to-convert-multiple-images-to-png-or-jpeg-on-macos/"/>
        <id>https://sgoel.dev/posts/how-to-convert-multiple-images-to-png-or-jpeg-on-macos/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/how-to-convert-multiple-images-to-png-or-jpeg-on-macos/">&lt;p&gt;I recently discovered a neat little feature built into macOS that lets you
convert multiple images from one format to another in one go.&lt;&#x2F;p&gt;
&lt;p&gt;In Finder, select the image(s) you want to convert and right-click (also called
the &quot;two-finger&quot; click) and select &quot;Quick Actions&quot; followed by &quot;Convert Image&quot;
in the submenu that shows up.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;sgoel.dev&#x2F;posts&#x2F;how-to-convert-multiple-images-to-png-or-jpeg-on-macos&#x2F;quick-actions.png&quot; alt=&quot;Quick Actions - macOS&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This should open up a new window that looks like this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;sgoel.dev&#x2F;posts&#x2F;how-to-convert-multiple-images-to-png-or-jpeg-on-macos&#x2F;convert-image.png&quot; alt=&quot;Convert Image - macOS&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Now, you can select the target format (JPEG, PNG, or HEIF) and the image size
(small, medium, large, or original) and click on the blue &quot;Convert to&quot; button.
And that&#x27;s it!&lt;&#x2F;p&gt;
&lt;p&gt;In the past, before I switched to using macOS as my daily driver, I always had
to Google for &quot;how to convert image to png in linux&quot; to do this. Some search
results would send me to online conversion websites where I could upload my
images and download the results, which I wasn&#x27;t interested in for privacy
reasons. The other results would either direct me to webpages that linked to a
list of desktop applications for this purpose, or they would document the
command-line arguments to &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;imagemagick.org&#x2F;index.php&quot;&gt;ImageMagick&lt;&#x2F;a&gt;&#x27;s &lt;code&gt;convert&lt;&#x2F;code&gt; command.&lt;&#x2F;p&gt;
&lt;p&gt;Installing desktop applications on Linux wasn&#x27;t always straight-forward for some
reason. &lt;code&gt;convert&lt;&#x2F;code&gt; would always do the job, but it was a bit of a hassle to
always look up the command line arguments and their positions. Besides, I found
this Google search to be a huge waste of time given that the task isn&#x27;t &lt;em&gt;that&lt;&#x2F;em&gt;
complicated.&lt;&#x2F;p&gt;
&lt;p&gt;macOS makes it so much simpler.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Automatically balancing Beancount DKB transactions</title>
        <published>2022-08-16T00:00:00+00:00</published>
        <updated>2022-08-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/automatically-balancing-beancount-dkb-transactions/"/>
        <id>https://sgoel.dev/posts/automatically-balancing-beancount-dkb-transactions/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/automatically-balancing-beancount-dkb-transactions/">&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;beancount.github.io&quot;&gt;Beancount&lt;&#x2F;a&gt; is one of my favorite packages in the Python ecosystem.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-is-beancount&quot;&gt;What is Beancount?&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;ve written about it in much more detail in &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;personalfinancespython.com&quot;&gt;other places&lt;&#x2F;a&gt; on the internet, but
if you&#x27;re new to it, Beancount is a PyPI package that provides a set of tools
for you to keep track of your personal finances using Python.&lt;&#x2F;p&gt;
&lt;p&gt;The entire workflow revolves around a &lt;code&gt;.beancount&lt;&#x2F;code&gt; file on your disk that
contains &lt;em&gt;all&lt;&#x2F;em&gt; your financial transactions (really, &quot;all&quot; as in
&quot;from-the-beginning-of-time all&quot;) specified using the double-entry bookkeeping
format. Keeping it up to date involves the following steps:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Download all your financial activity (at regular time intervals) from your
bank&#x27;s website as PDF&#x2F;CSV&#x2F;whatever-your-bank-allows exports.&lt;&#x2F;li&gt;
&lt;li&gt;Convert this data into a format that Beancount understand (this is the part
which may or may not involve custom Python code).&lt;&#x2F;li&gt;
&lt;li&gt;Add&#x2F;append the output of step #2 to &lt;code&gt;you.beancount&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;At the end, &lt;code&gt;you.beancount&lt;&#x2F;code&gt; contains all your financial activity where you can
use all the tools that Beancount (and the ecosystem) provides to run all sorts
of analysis on it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;beancount-dkb&quot;&gt;Beancount DKB&lt;&#x2F;h2&gt;
&lt;p&gt;One of my main bank accounts is with &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.dkb.de&quot;&gt;DKB&lt;&#x2F;a&gt;. So to get Beancount working with DKB
data exports, I &lt;a href=&quot;https:&#x2F;&#x2F;sgoel.dev&#x2F;posts&#x2F;beancount-dkb&#x2F;&quot;&gt;wrote and currently maintain beancount-dkb&lt;&#x2F;a&gt;, which provides a
PyPI package to convert DKB CSV exports to the Beancount format.&lt;&#x2F;p&gt;
&lt;p&gt;So if one of the lines of your DKB CSV exports looks as follows:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;quot;15.08.2022&amp;quot;;&amp;quot;15.08.2022&amp;quot;;&amp;quot;Lastschrift&amp;quot;;&amp;quot;REWE Filialen Voll&amp;quot;;&amp;quot;REWE SAGT DANKE.&amp;quot;;&amp;quot;DE00000000000000000000&amp;quot;;&amp;quot;AAAAAAAA&amp;quot;;&amp;quot;-15,37&amp;quot;;&amp;quot;000000000000000000&amp;quot;;&amp;quot;0000000000000000000000&amp;quot;;&amp;quot;&amp;quot;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;... running it through an &lt;code&gt;ECImporter&lt;&#x2F;code&gt; instance makes Beancount output something
like the following:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;beancount&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2022&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;08&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;15 *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;REWE Filialen Voll&amp;quot; &amp;quot;REWE SAGT DANKE&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Assets&lt;&#x2F;span&gt;&lt;span&gt;:DKB:EC&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;15.37&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In terms of double-entry bookkeeping (which is what Beancount relies on), this
is halfway there. We still need to add a second leg to this transaction to
balance it out such that the total sum is zero.&lt;&#x2F;p&gt;
&lt;p&gt;This step is something we usually do manually. But it doesn&#x27;t always have to be.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;pattern-matching-against-the-payee&quot;&gt;Pattern matching against the Payee&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;em&gt;Disclaimer: I stole the following idea from &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mtlynch&#x2F;beancount-mercury&quot;&gt;beancount-mercury&lt;&#x2F;a&gt; written by
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;mtlynch.io&quot;&gt;Michael Lynch&lt;&#x2F;a&gt;.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;If we look at the original transaction, the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;beancount.github.io&#x2F;docs&#x2F;beancount_language_syntax.html#payee-narration&quot;&gt;payee&lt;&#x2F;a&gt; is always the same. And even
if won&#x27;t always be the same, it&#x27;s at least going to contain the same pattern.&lt;&#x2F;p&gt;
&lt;p&gt;We can make use of this fact to tell the DKB importers that if the payee string
contains a specific value, automatically add a second posting to a separate
matching account (which is what we would have done manually anyway). This is
specifically the feature I ended up pushing to PyPI &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pypi.org&#x2F;project&#x2F;beancount-dkb&#x2F;0.14.0&#x2F;&quot;&gt;last weekend&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The package distributes two importers: &lt;code&gt;ECImporter&lt;&#x2F;code&gt; (for the German EC accounts)
and &lt;code&gt;CreditImporter&lt;&#x2F;code&gt; (for credit cards).&lt;&#x2F;p&gt;
&lt;p&gt;After the update from last weekend, initializing &lt;code&gt;ECImporter&lt;&#x2F;code&gt; looks as follows:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;CONFIG&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ECImporter(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;#39;DE1234567898765432100&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;#39;Assets:DKB:EC&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;#39;EUR&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;        file_encoding&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;ISO-8859-1&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;        # New&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;        payee_patterns&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;REWE Filialen Voll&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;Expenses:Supermarket:REWE&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        ],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;payee_patterns&lt;&#x2F;code&gt; is a new parameter that should contain a list of &lt;code&gt;(regex, account)&lt;&#x2F;code&gt; tuples as an argument. With this in place, &lt;code&gt;ECImporter&lt;&#x2F;code&gt; compiles the
given regular expressions and uses this list to check if the payee of a given
transaction matches any of the given values. If that is the case, the second
posting (referencing the &lt;code&gt;account&lt;&#x2F;code&gt;) is automatically inserted.&lt;&#x2F;p&gt;
&lt;p&gt;Similarly, the &lt;code&gt;CreditImporter&lt;&#x2F;code&gt; has been adjusted as follows:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;CONFIG&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    CreditImporter(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;#39;1234********5678&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;#39;Assets:DKB:Credit&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;#39;EUR&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;        file_encoding&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;ISO-8859-1&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;        # New&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;        description_patterns&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;REWE SAGT DANKE&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;Expenses:Supermarket:REWE&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        ],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The idea is the same as in &lt;code&gt;ECImporter&lt;&#x2F;code&gt;. Just that instead of &lt;code&gt;payee_patterns&lt;&#x2F;code&gt;,
&lt;code&gt;CreditImporter&lt;&#x2F;code&gt; relies on &lt;code&gt;description_patterns&lt;&#x2F;code&gt; because at the time of this
writing, the CSV exports from DKB credit card accounts do not include a payee
field. So the transaction&#x27;s description string is all we have to work with.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;result&quot;&gt;Result&lt;&#x2F;h2&gt;
&lt;p&gt;With this updated API and the updated importers, &lt;code&gt;beancount-dkb&lt;&#x2F;code&gt; automatically
outputs the following transaction:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;beancount&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2022&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;08&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;15 *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;REWE Filialen Voll&amp;quot; &amp;quot;REWE SAGT DANKE&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Assets&lt;&#x2F;span&gt;&lt;span&gt;:DKB:EC&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;15.37&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Expenses&lt;&#x2F;span&gt;&lt;span&gt;:Supermarket:REWE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The fact that I don&#x27;t have to add this one line myself reduces my work by about
a half. Thanks &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;mtlynch.io&quot;&gt;Michael&lt;&#x2F;a&gt;, for coming up with the idea!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Replacing GNU Stow with Dotbot</title>
        <published>2022-07-24T00:00:00+00:00</published>
        <updated>2022-07-24T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/replacing-gnu-stow-with-dotbot/"/>
        <id>https://sgoel.dev/posts/replacing-gnu-stow-with-dotbot/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/replacing-gnu-stow-with-dotbot/">&lt;p&gt;One of my guilty pleasures is maintaining my &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;dotfiles&quot;&gt;dotfiles&lt;&#x2F;a&gt;. This is one project I
can yak-shave and play with as much as I want without caring about the &lt;em&gt;actual
impact&lt;&#x2F;em&gt; or in general the usefulness of the whole endeavour. So this weekend I
decided to indulge myself.&lt;&#x2F;p&gt;
&lt;p&gt;Part of the process was cleaning up the older dotfiles and updating them to
newer preferences. And the other part (which is the subject of this blog post)
was the installation process, which is basically the process of how these files
get from my &lt;code&gt;dotfiles&lt;&#x2F;code&gt; repository to my home folder.&lt;&#x2F;p&gt;
&lt;p&gt;There are many different ways you can do this. So far, I&#x27;ve been using &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;stow&#x2F;&quot;&gt;GNU
Stow&lt;&#x2F;a&gt; for this purpose.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;gnu-stow&quot;&gt;GNU Stow&lt;&#x2F;h2&gt;
&lt;p&gt;Stow is a useful little program to help you manage symbolic links.&lt;&#x2F;p&gt;
&lt;p&gt;Given a source directory and a target directory, it is able to add symbolic
links into the target directory that point back to the source. So if you&#x27;re
maintaining your dotfiles in a git repository and want to have your home folder
link back to them, Stow is perfect for this.&lt;&#x2F;p&gt;
&lt;p&gt;As an example, assume your &lt;code&gt;dotfiles&lt;&#x2F;code&gt; project contains a &lt;code&gt;nvim&lt;&#x2F;code&gt; folder for your
Neovim configuration files and that it should be linked to &lt;code&gt;~&#x2F;.config&#x2F;nvim&lt;&#x2F;code&gt;,
Stow can do that for you if you run &lt;code&gt;stow nvim --target $HOME&#x2F;.config&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;If you maintain multiple dotfiles, your setup process might end up in a shell
script resembling the following:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;mkdir&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; ~&#x2F;.config&#x2F;kitty&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;stow&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; kitty&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --target&lt;&#x2F;span&gt;&lt;span&gt; $HOME&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&#x2F;.config&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;mkdir&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; ~&#x2F;.config&#x2F;nvim&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;stow&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; nvim&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --target&lt;&#x2F;span&gt;&lt;span&gt; $HOME&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&#x2F;.config&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;stow&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; zsh&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --target&lt;&#x2F;span&gt;&lt;span&gt; $HOME&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;While this does the job, the one thing that never really sat well with me (&lt;em&gt;no
actual impact&lt;&#x2F;em&gt;, remember?) was the fact that it&#x27;s a shell script.&lt;&#x2F;p&gt;
&lt;p&gt;Generally speaking, I&#x27;m allergic to shell scripts. I don&#x27;t write them on a
regular basis, so when I do have to work with one, I constantly need to look up
the documentation of the most primitive things (eg. &quot;bash if else syntax&quot;) which
is really unproductive. Debugging shell scripts is also not the most fun
activity. So in general, I try to avoid them as long as I can.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;dotbot&quot;&gt;Dotbot&lt;&#x2F;h2&gt;
&lt;p&gt;This weekend I was looking for a dotfiles manager (again, &lt;em&gt;no actual impact&lt;&#x2F;em&gt;)
and found &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;anishathalye&#x2F;dotbot&quot;&gt;dotbot&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Dotbot is a &lt;strong&gt;dot&lt;&#x2F;strong&gt;file &lt;strong&gt;bo&lt;&#x2F;strong&gt;o&lt;strong&gt;t&lt;&#x2F;strong&gt;strapper that takes in a (YAML)
configuration file that describes the symbolic link definitions (and some other
metadata) and updates those definitions on disk. The functionality it offered
looked almost exactly like what I wanted so I decided to give it a try.&lt;&#x2F;p&gt;
&lt;p&gt;The installation feels a bit clunky (the default instructions recommend
installing it as a git submodule) but luckily it&#x27;s published on PyPI as a
package so we can use &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;pypa&#x2F;pipx&quot;&gt;&lt;code&gt;pipx&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; to install it.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; pipx install dotbot&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The next step was to remove the &lt;code&gt;stow&lt;&#x2F;code&gt; invocations in favor of a dotbot
configuration file. The most recent version of my config file is available
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;dotfiles&#x2F;blob&#x2F;main&#x2F;install.conf.yaml&quot;&gt;here&lt;&#x2F;a&gt;, but essentially it looks something like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt; defaults&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;    link&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;      relink&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; true&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt; link&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;    ~&#x2F;.config&#x2F;kitty&#x2F;kitty.conf&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; kitty&#x2F;kitty.conf&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    # ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;    ~&#x2F;.zshrc&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; zsh&#x2F;zshrc&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt; create&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; ~&#x2F;.config&#x2F;kitty&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;defaults&lt;&#x2F;code&gt; section contains some &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;anishathalye&#x2F;dotbot#defaults&quot;&gt;default configuration options&lt;&#x2F;a&gt; which I&#x27;m
not going to write about here as the README does a much better job of explaining
what it is for.&lt;&#x2F;p&gt;
&lt;p&gt;The real work happens inside &lt;code&gt;link&lt;&#x2F;code&gt;, which is basically a key&#x2F;value map of the
dotfile&#x27;s destination and the source. This is where you can tell dotbot what
should be linked and where. Lastly, the &lt;code&gt;create&lt;&#x2F;code&gt; section contains a list of
directories that should be created prior to any linking taking place.&lt;&#x2F;p&gt;
&lt;p&gt;With this place, dotbot can be invoked on the command line to set up all the
symbolic links in one go:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; dotbot&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --config-file&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; install.conf.yaml&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Link&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; exists ~&#x2F;.config&#x2F;kitty&#x2F;kitty.conf&lt;&#x2F;span&gt;&lt;span&gt; -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &#x2F;home&#x2F;siddhant&#x2F;Work&#x2F;projects&#x2F;dotfiles&#x2F;kitty&#x2F;kitty.conf&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Link&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; exists ~&#x2F;.zshrc&lt;&#x2F;span&gt;&lt;span&gt; -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &#x2F;home&#x2F;siddhant&#x2F;Work&#x2F;projects&#x2F;dotfiles&#x2F;zsh&#x2F;zshrc&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;All&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; links have been set up&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Path&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; exists &#x2F;home&#x2F;siddhant&#x2F;.config&#x2F;kitty&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;All&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; paths have been set up&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;==&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; All tasks executed successfully&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This feels &lt;em&gt;much&lt;&#x2F;em&gt; cleaner than the original shell script. Of course, given how
many dotfiles I have (which is not that many), the original script would have
sufficed. But as I mentioned before, none of this is an exercise in usefulness,
so I personally consider this change perfectly acceptable. 🌝&lt;&#x2F;p&gt;
&lt;h2 id=&quot;encrypted-files&quot;&gt;Encrypted files&lt;&#x2F;h2&gt;
&lt;p&gt;The one thing I&#x27;m missing from dotbot is support for encrypted files. It would
be nice if you could add encrypted files to the git repository and have dotbot
decrypt them automatically before putting them in place.&lt;&#x2F;p&gt;
&lt;p&gt;Note that there is a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;anishathalye&#x2F;dotbot#shell&quot;&gt;&lt;code&gt;shell&lt;&#x2F;code&gt; directive&lt;&#x2F;a&gt; for running shell commands. So in
theory, we could add a shell command for decrypting a sensitive file before the
symbolic links are established. Unfortunately, it seems like these commands are
run &lt;em&gt;after&lt;&#x2F;em&gt; the linking has been done, while we want the decryption to happen
&lt;em&gt;before&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This means that I&#x27;ve been forced to add back a &lt;code&gt;setup.sh&lt;&#x2F;code&gt; file which looks like
this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# decrypt crestic password&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;age&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --decrypt --identity&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; ~&#x2F;.age&#x2F;keys.txt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --output&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; crestic&#x2F;password crestic&#x2F;password.enc&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# install&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;dotbot&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --config-file&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; install.conf.yaml&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The first line uses &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;FiloSottile&#x2F;age&quot;&gt;&lt;code&gt;age&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; to decrypt a password file and put it in a location
where dotbot can read it and eventually place the link in the right location.
This is less than ideal, but it works.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;Overall I have really come to like dotbot. It does everything I need it to do
and some more.&lt;&#x2F;p&gt;
&lt;p&gt;There is also &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;anishathalye&#x2F;dotbot#plugins&quot;&gt;support for plugins&lt;&#x2F;a&gt; and there are a bunch of &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;anishathalye&#x2F;dotbot&#x2F;wiki&#x2F;Plugins&quot;&gt;nice plugins&lt;&#x2F;a&gt; out
there as well. For instance, you can manage your &lt;code&gt;crontab&lt;&#x2F;code&gt; schedule or specify a
list of &lt;code&gt;apt&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;snap&lt;&#x2F;code&gt; packages to be installed automatically. While not a strict
necessity, it&#x27;s nice to know that these things are there.&lt;&#x2F;p&gt;
&lt;p&gt;In any case, I do see myself using dotbot going forward. At least until the next
time I feel the need to yak-shave.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>The tools I used to write an ebook</title>
        <published>2021-10-04T00:00:00+00:00</published>
        <updated>2021-10-04T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/the-tools-i-used-to-write-an-ebook/"/>
        <id>https://sgoel.dev/posts/the-tools-i-used-to-write-an-ebook/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/the-tools-i-used-to-write-an-ebook/">&lt;p&gt;After writing and launching a technical ebook, I&#x27;ve had a few people ask me
about the tools I used in the process. Instead of answering individually to
everyone, I thought I would document the entire process here and send a link out
instead.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-big-picture&quot;&gt;The Big Picture&lt;&#x2F;h2&gt;
&lt;p&gt;It&#x27;s easier to talk about the entire process by breaking it down into three
different stages: the input, the pipeline, and the output.&lt;&#x2F;p&gt;
&lt;p&gt;Input is the source code of the book - basically the inner content that the
readers end up reading. The Pipeline is the process I used to generate the final
artifacts ready for distribution. And the Output is how those artifacts end up
in the hands of the readers.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s look at them one by one.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-input&quot;&gt;The Input&lt;&#x2F;h2&gt;
&lt;p&gt;At its core, the book is a bunch of plain-text &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Markdown&quot;&gt;Markdown&lt;&#x2F;a&gt; files.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;~&#x2F;W&#x2F;p&#x2F;personal-finances-python main ❯ tree book&#x2F;src&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;book&#x2F;src&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── 00-about.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── 01-introduction.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── 02-plain-text-accounting.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── 03-beancount.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── 04-workflow.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── 05-testing.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── 06-reporting.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;└── 07-closing-thoughts.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;0 directories, 8 files&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Each chapter is its own Markdown file. There are some code samples and images
that these files reference which are also baked in to the final artifacts. But
when it comes to the &quot;source code&quot; of the ebook, that&#x27;s about it. Nothing
special there.&lt;&#x2F;p&gt;
&lt;p&gt;I really like Markdown because (for me) it strikes exactly the right balance
between size and expressiveness. The language is simple and small enough that
you don&#x27;t have to make a lot of effort keeping the syntax in your head. This is
in stark contrast to something like &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;ReStructuredText&quot;&gt;RST&lt;&#x2F;a&gt; where it&#x27;s really hard to remind
yourself what primitives produce what output. At the same time it&#x27;s powerful
enough that you don&#x27;t feel like you can&#x27;t express something - at least most of
the time.&lt;&#x2F;p&gt;
&lt;p&gt;Because of this reason, most of my writing for the past few years has ended up
being in Markdown. So that&#x27;s what I ended up using for the ebook too.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-pipeline&quot;&gt;The Pipeline&lt;&#x2F;h2&gt;
&lt;p&gt;This is the slightly more interesting part where I have both good and not so
good things to say.&lt;&#x2F;p&gt;
&lt;p&gt;I used a program called &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pandoc.org&#x2F;&quot;&gt;pandoc&lt;&#x2F;a&gt; to convert the Markdown files into PDF and ePub
files. Pandoc is a swiss-army knife of document conversion. You can take a
document in one format and have pandoc convert it to a different (supported)
format by passing it through pandoc.&lt;&#x2F;p&gt;
&lt;p&gt;In my case, the input format was Markdown and the final output was PDF and ePub.
While the ePub conversion was a breeze, the PDF formatting took quite some time
to get right (more on that later).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-good&quot;&gt;The good&lt;&#x2F;h3&gt;
&lt;p&gt;Pandoc is extremely powerful. The process of converting documents from one
format to another using just one command sometimes felt like cheating.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pandoc.org&#x2F;MANUAL.html&quot;&gt;manual&lt;&#x2F;a&gt; is also very useful and comprehensive. In my experience, if Pandoc
is capable of feature X, it&#x27;s extremely likely that you&#x27;ll find it in the
manual.&lt;&#x2F;p&gt;
&lt;p&gt;The package is relatively straight-forward to install on most UNIX systems. For
PDF generation, you&#x27;ll most likely have to install a TeX distribution in
addition. If installing TeX using a package manager is not really your taste,
there are also official docker images that you can use.&lt;&#x2F;p&gt;
&lt;p&gt;Pandoc also supports &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pandoc.org&#x2F;filters.html&quot;&gt;filters&lt;&#x2F;a&gt; which lets you do useful things with the output.
For instance, I used the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tomduck&#x2F;pandoc-fignos&quot;&gt;pandoc-fignos&lt;&#x2F;a&gt; filter to add numbers to figures and
reference them in the document. This was really useful to have text in the PDF
link to the image.&lt;&#x2F;p&gt;
&lt;p&gt;Lastly, pandoc has been around for quite some time and has seen quite a bit of
usage. This means that if you&#x27;re running into an issue, it&#x27;s very likely that
someone else already ran into it so there might be a relevant Github thread or a
StackOverflow question, maybe also with a solution. This makes it easier to get
a headstart in case you&#x27;re stuck on something.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-not-so-good&quot;&gt;The not so good&lt;&#x2F;h3&gt;
&lt;p&gt;The not so good was mainly getting the PDF formatting right. Like I mentioned
before, the ePub generation was a breeze. But getting a presentable PDF was a
massive pain.&lt;&#x2F;p&gt;
&lt;p&gt;At its core, Pandoc is a document converter. It&#x27;s not an ebook formatting tool.
This means that while technically you &lt;em&gt;can&lt;&#x2F;em&gt; generate ebook PDFs, Pandoc doesn&#x27;t
know that it&#x27;s working with an ebook (unless of course you&#x27;re generating an
ePub file).&lt;&#x2F;p&gt;
&lt;p&gt;This means that the default PDF output is really plain &#x2F; bland. If you want the
formatting to look a certain way, expect to spend some &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;jdhao.github.io&#x2F;2019&#x2F;05&#x2F;30&#x2F;markdown2pdf_pandoc&#x2F;&quot;&gt;time&lt;&#x2F;a&gt; and &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;learnbyexample.github.io&#x2F;customizing-pandoc&#x2F;&quot;&gt;effort&lt;&#x2F;a&gt; to
get things running. In my case, I ended up spending &lt;em&gt;days&lt;&#x2F;em&gt; looking through the
pandoc manual and searching for the web to fix specific formatting issues I was
running into.&lt;&#x2F;p&gt;
&lt;p&gt;How do you properly insert a cover image at the front? How do you format
blockquotes so they look a specific way? How do you specify the header and
footer texts? What all &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ctan.org&#x2F;pkg&#x2F;listings?lang=en&quot;&gt;listings&lt;&#x2F;a&gt; settings do you need to adjust so that code
blocks look &lt;em&gt;good&lt;&#x2F;em&gt; and consistent with inline code?&lt;&#x2F;p&gt;
&lt;p&gt;These are all questions I wish I wouldn&#x27;t have had to spend time figuring out
answers to. But unfortunately I did. If you&#x27;re proficient with LaTeX, you might
have a different experience because pandoc uses LaTeX to generate PDFs from
Markdown. However, the last time I touched LaTeX was about 7-8 years ago so as
of this writing, I wouldn&#x27;t really call myself proficient there.&lt;&#x2F;p&gt;
&lt;p&gt;So almost every single issue I ran into was a bit of a pain. I would do an
online search, find a solution, which was often in the form of a LaTeX snippet
which I was supposed to put in the original source&#x27;s preamble. Over time, all
those LaTeX snippets added up and it became really tricky to figure out if a new
issue was because of some weird combination of the existing snippets or some
other reason entirely.&lt;&#x2F;p&gt;
&lt;p&gt;At some point I gave up trying to fine-tune these settings by hand and started
looking for a boilerplate instead. In hindsight, this should have been step 1.
😁&lt;&#x2F;p&gt;
&lt;p&gt;I found an excellent &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;Wandmalfarbe&#x2F;pandoc-latex-template&quot;&gt;Pandoc &#x2F; LaTeX template&lt;&#x2F;a&gt; which I ended up using for my
ebook. This almost instantly solved all the formatting issues. If you&#x27;re writing
an ebook and are planning to generate a PDF using Pandoc, I highly recommend
this template. Most of the formatting issues you could care about can be
controlled using variables in this template. And the final output looks very
nice.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-output&quot;&gt;The Output&lt;&#x2F;h2&gt;
&lt;p&gt;There&#x27;s honestly not much to write in this section.&lt;&#x2F;p&gt;
&lt;p&gt;I chose &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;gumroad.com&#x2F;&quot;&gt;Gumroad&lt;&#x2F;a&gt; to distribute the final ebook artifacts and I really like
them. 😀&lt;&#x2F;p&gt;
&lt;p&gt;Gumroad is an excellent service which lets you sell digital products. It&#x27;s
mostly intended at online creators. Whatever product you want to sell, you can
upload it and set up a product page for it, and can start selling immediately.
You can get paid using either a PayPal account or direct bank transfer,
depending on the country you&#x27;re living in. And they even take care of handling
taxes for you.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re considering selling a digital product, make sure to give them a try.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;That&#x27;s about it! I think those are pretty much all the technologies I used to
build and sell my ebook.&lt;&#x2F;p&gt;
&lt;p&gt;If there&#x27;s anything you&#x27;d like to know more about, feel free to @&#x2F;DM me on
Twitter!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>How you can track your personal finances using Python 🐍</title>
        <published>2021-08-29T00:00:00+00:00</published>
        <updated>2021-08-29T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/how-you-can-track-your-personal-finances-using-python/"/>
        <id>https://sgoel.dev/posts/how-you-can-track-your-personal-finances-using-python/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/how-you-can-track-your-personal-finances-using-python/">&lt;p&gt;In this post, I&#x27;d like to describe how you can track your personal finances
using a workflow that is highly focused on data privacy, is 100% self-hosted,
and uses only the Python ecosystem.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m also secretly hoping that some of you find this interesting enough to
explore it in &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;personalfinancespython.com&quot;&gt;more detail&lt;&#x2F;a&gt;. 🙂&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;We all know that money is important. Having money buys us freedom and lack of
money is stressful. So how do we know whether we&#x27;re doing good on that front?&lt;&#x2F;p&gt;
&lt;p&gt;The answer is simple: we track our money.&lt;&#x2F;p&gt;
&lt;p&gt;We keep an eye on how much money is coming in to our accounts, how much is going
out, and when and how often those events are happening. We keep an eye out on
trends, what are the recurring expenses, how many of those are necessary, so on
and so forth.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s a good chance that you&#x27;re doing this already. There are plenty of
off-the-shelf solutions one can pick from for this purpose. There are mobile
and SaaS apps that can connect to all your bank accounts, import all your
financial transactions, and show you consolidated data.&lt;&#x2F;p&gt;
&lt;p&gt;Not that there&#x27;s anything particularly wrong with such apps. I&#x27;m instead of the
opinion that my financial data (from across all my bank accounts) is something
&lt;strong&gt;only I&lt;&#x2F;strong&gt; should have consolidated access to. Financial data is one of the most
privata data I own. So limiting the possible attack vectors sounds to me like
an obvious choice.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re out searching for such software and limit your search to open-source
only solutions, you&#x27;re most likely going to come across &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;plaintextaccounting.org&quot;&gt;Plain Text Accounting&lt;&#x2F;a&gt;,
which is something I&#x27;ll describe in this post.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;big-and-quick-picture&quot;&gt;Big (and Quick) picture&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;TL:DR&lt;&#x2F;strong&gt;: Maintaining &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Double-entry_bookkeeping&quot;&gt;double-entry accounting&lt;&#x2F;a&gt; based records of your financial
transactions in plain-text files is the way to go.&lt;&#x2F;p&gt;
&lt;p&gt;Double-entry accounting is a great way to track your finances. In this system,
the flow of money between accounts is represented using transactions. You can
think of a transaction as an &quot;entry&quot; of sorts that talks about a specific
instance of money flowing between accounts. In most cases, transactions are
composed of two &quot;legs&quot;, where one leg is the &lt;strong&gt;credit&lt;&#x2F;strong&gt; side and the other one
is the &lt;strong&gt;debit&lt;&#x2F;strong&gt; side.&lt;&#x2F;p&gt;
&lt;p&gt;One of the most important rules of double-entry accounting is that the sum of
the amounts of the individual legs in a transaction must be zero. A transaction
is said to be &quot;balanced&quot; if this rule is satisfied.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s a sample transaction to help you visualize this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;beancount&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2021&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01 *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;AMAZON.DE&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Assets&lt;&#x2F;span&gt;&lt;span&gt;:MyBank&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;42.00&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Expenses&lt;&#x2F;span&gt;&lt;span&gt;:Amazon&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;   42.00&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This transaction represents an Amazon purchase I made where the money was
deducted from one of my accounts (called &lt;code&gt;Assets:MyBank&lt;&#x2F;code&gt;) and added to an Amazon
expense account.&lt;&#x2F;p&gt;
&lt;p&gt;A collection of such transactions is what makes up your financial ledger.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;double-entry-accounting-hearts-python-beancount&quot;&gt;Double-entry Accounting ♥️ Python = Beancount&lt;&#x2F;h2&gt;
&lt;p&gt;The Python ecosystem contains a really neat package called &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pypi.org&#x2F;project&#x2F;beancount&quot;&gt;Beancount&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Beancount is a command-line implementation of the double-entry accounting
system that works on top of plain-text files. It mainly provides the following
three things:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;basic primitives for working with money (eg. data structures like Accounts,
Transactions, Postings, etc.)&lt;&#x2F;li&gt;
&lt;li&gt;a language specification for defining financial transactions in a plain-text
format (the snippet I showed earlier is valid Beancount code)&lt;&#x2F;li&gt;
&lt;li&gt;command-line scripts that tie everything together&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;As with most Python packages, getting started is as easy as creating a virtual
environment and running &lt;code&gt;pip install beancount&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-does-the-ledger-look-like&quot;&gt;What does the ledger look like?&lt;&#x2F;h2&gt;
&lt;p&gt;I wrote earlier that one of the main things that Beancount provides is a
language specification for defining financial transactions in a plain-text
format.&lt;&#x2F;p&gt;
&lt;p&gt;What does this format look like? Here&#x27;s a quick example:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;beancount&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;option &amp;quot;title&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Alice&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;option &amp;quot;operating_currency&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;EUR&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;; Accounts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2021&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01 open Assets&lt;&#x2F;span&gt;&lt;span&gt;:MyBank:Checking&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2021&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01 open Expenses&lt;&#x2F;span&gt;&lt;span&gt;:Rent&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2021&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01 *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Landlord&amp;quot; &amp;quot;Thanks for the rent&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Assets&lt;&#x2F;span&gt;&lt;span&gt;:MyBank:Checking&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;     -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1000.00&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Expenses&lt;&#x2F;span&gt;&lt;span&gt;:Rent&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;               1000.00&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;What you see here is a very basic financial ledger of an imaginary person.&lt;&#x2F;p&gt;
&lt;p&gt;In the first few lines we specify some metadata. Then we open (initialize) two
Accounts. And the last three lines define a transaction where rent was deducted
from one of those accounts and debited to the other one.&lt;&#x2F;p&gt;
&lt;p&gt;Note that the account names don&#x27;t always have to correspond one-to-one to
real-world accounts. You can define as many accounts as you like, each for a
specific purpose. For instance, you can have one account to track your
supermarket expenses, one for rent, one for Netflix, so on and so forth.&lt;&#x2F;p&gt;
&lt;p&gt;As I said, this is a very basic example. Real-world ledger files tend to be much
longer.&lt;&#x2F;p&gt;
&lt;p&gt;For instance, at the time of this writing my personal &lt;code&gt;.beancount&lt;&#x2F;code&gt; file contains
close to 21,000 lines.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;~&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;Work&#x2F;finances main ❯ wc -l goel.beancount&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;20996&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; goel.beancount&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;how-does-the-data-flow&quot;&gt;How does the data flow?&lt;&#x2F;h2&gt;
&lt;p&gt;I hope that at this point you have a basic understanding of Beancount and
double-entry accounting. In this section, I&#x27;ll quickly run you through how data
flows in such a system.&lt;&#x2F;p&gt;
&lt;p&gt;The core idea with Beancount is that the user is responsible for storing all
their financial transactions inside their &lt;code&gt;.beancount&lt;&#x2F;code&gt; file. This file acts as
the single source of truth for all your financial data from all your banks.&lt;&#x2F;p&gt;
&lt;p&gt;So how do your transactions from your bank(s) end up in this file? In a series
of three simple steps:&lt;&#x2F;p&gt;
&lt;h3 id=&quot;1-download-transactions-from-your-bank&quot;&gt;1. Download transactions from your bank&lt;&#x2F;h3&gt;
&lt;p&gt;Almost every bank allows you to export your data in some form. You should be
able to log in to your bank&#x27;s website, select a timeframe, and download all the
transactions in a given file format. This usually tends to be CSV, but you can
also download a PDF or sometimes an &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Open_Financial_Exchange&quot;&gt;OFX&lt;&#x2F;a&gt; file.&lt;&#x2F;p&gt;
&lt;p&gt;This is the first step in a Beancount-driven workflow: download all your
original data to your machine.&lt;&#x2F;p&gt;
&lt;p&gt;Note that ideally this should be something that Python can parse without much
ceremony. For instance, if I would have the choice between CSV and PDF, I would
most likely pick a CSV download. Not because Python cannot parse PDF data
(because &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pypi.org&#x2F;search&#x2F;?q=pdf&quot;&gt;it can&lt;&#x2F;a&gt;) but because CSV is a much simpler data format than PDF.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;2-convert-transactions-to-the-beancount-format&quot;&gt;2. Convert transactions to the Beancount format&lt;&#x2F;h3&gt;
&lt;p&gt;The next step is to take these CSV files and convert this data into a format
that Beancount understands.&lt;&#x2F;p&gt;
&lt;p&gt;Beancount provides an &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;beancount.github.io&#x2F;docs&#x2F;importing_external_data.html&quot;&gt;importer framework&lt;&#x2F;a&gt; to help with this process. Importers
are Python programs which take a source file (eg. the downloaded CSV), parse it,
and convert the data into data structures that Beancount provides. A text
representation of these data structures is what makes up your personal ledger.&lt;&#x2F;p&gt;
&lt;p&gt;Now, obviously Beancount does not know about all the banks on the planet and
what their CSV exports look like and how they should be parsed. This is where
the Python developer inside you can shine.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;Importer&lt;&#x2F;code&gt; classes shipped with Beancount are more like protocols. The base
class is literally called &lt;code&gt;ImporterProtocol&lt;&#x2F;code&gt;. They define method signatures and
leave the implementation up to you. This way, you can inherit the base
&lt;code&gt;ImporterProtocol&lt;&#x2F;code&gt; class for your own importer, override the relevant methods,
and let Beancount know of the existence of these importer classes using a
configuration file.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; beancount.ingest.importer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; ImporterProtocol&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; convert_to_beancount_data_structs&lt;&#x2F;span&gt;&lt;span&gt;(line):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;    Implement me!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; MyBankImporter&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;ImporterProtocol&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; extract&lt;&#x2F;span&gt;&lt;span&gt;(self, file):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        Read and parse the file and convert the original data into Beancount&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        data structures&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        entries&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; []&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        for&lt;&#x2F;span&gt;&lt;span&gt; line&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; file&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            entries.append(convert_to_beancount_data_structs(line))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span&gt; entries&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Depending on what banks you have accounts with, you can define multiple such
importer classes and let Beancount know of their existence in a configuration
file. Everything else will be handled for you automatically using the command
line scripts included in Beancount.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;3-balance-the-transactions&quot;&gt;3. Balance the transactions&lt;&#x2F;h3&gt;
&lt;p&gt;The next step is also the most fun part (at least I find it fun).&lt;&#x2F;p&gt;
&lt;p&gt;We take the output of the previous step, pipe everything over to our
&lt;code&gt;.beancount&lt;&#x2F;code&gt; file, and &quot;balance&quot; transactions.&lt;&#x2F;p&gt;
&lt;p&gt;Recall that the flow of money in double-entry accounting is represented
using transactions involving at least two accounts. When you download CSVs from
your bank, each line in that CSV represents money that&#x27;s either incoming or
outgoing. That&#x27;s only one leg of a transaction (credit or debit). It&#x27;s up to us
to provide the other leg.&lt;&#x2F;p&gt;
&lt;p&gt;This act is called &quot;balancing&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;For instance, assume that the output of the previous step was the following
transaction:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;beancount&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2021&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01 *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Landlord&amp;quot; &amp;quot;Thanks for the rent&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Assets&lt;&#x2F;span&gt;&lt;span&gt;:MyBank:Checking&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1000.00&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The total amount of the legs in this transaction does not yet sum to zero. It&#x27;s
up to us to balance it.&lt;&#x2F;p&gt;
&lt;p&gt;Balancing involves figuring out what the transaction is about and assigning it
an equal and opposite leg. In this case we can see that this transaction has to
do with us paying rent. So the second leg should clearly contain the
&lt;code&gt;Expenses:Rent&lt;&#x2F;code&gt; account.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;beancount&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2021&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01 *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Landlord&amp;quot; &amp;quot;Thanks for the rent&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Assets&lt;&#x2F;span&gt;&lt;span&gt;:MyBank:Checking&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;     -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1000.00&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Expenses&lt;&#x2F;span&gt;&lt;span&gt;:Rent&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;               1000.00&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This process of balancing is something we do for all the transactions, so that
the entire ledger can be marked as balanced.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;4-that-s-it&quot;&gt;4. That&#x27;s it!&lt;&#x2F;h3&gt;
&lt;p&gt;That&#x27;s basically it. Those three steps, performed at regular intervals of time,
should ensure that your &lt;code&gt;.beancount&lt;&#x2F;code&gt; data contains all your financial
transactions and is in good shape.&lt;&#x2F;p&gt;
&lt;p&gt;I prefer doing this every month. So on the first Sunday of every month, I
prepare a fresh cup of coffee, download all the CSV files, run them through my
importers, and balance all the unbalanced transactions.&lt;&#x2F;p&gt;
&lt;p&gt;You might think that this is too much work. I have to admit that I had a similar
thought when I started out.&lt;&#x2F;p&gt;
&lt;p&gt;In my experience so far, it has been the exact opposite. The entire process has
never taken me more than 45 minutes to finish. Considering that I do this once
a month, the time investment seems more than fair. And the added benefit is that
by balancing these transactions by hand, I get a fairly good idea of what was
happening in my accounts in the previous month.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;visualization&quot;&gt;Visualization&lt;&#x2F;h2&gt;
&lt;p&gt;So far we talked about entering data into the system.&lt;&#x2F;p&gt;
&lt;p&gt;The real power of personal accounting software is in showing you insights from
that data. In this section I&#x27;ll quickly walk you through the two tools I end up
using the most: &lt;code&gt;bean-query&lt;&#x2F;code&gt; and Fava.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;1-bean-query&quot;&gt;1. &lt;code&gt;bean-query&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;bean-query&lt;&#x2F;code&gt; is a command line utility shipped with Beancount that lets you run
SQL-ish queries on your financial data. The query language is specific to
Beancount. But if you&#x27;re familiar with SQL, you&#x27;ll feel right at home with
&lt;code&gt;bean-query&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s an example run from my own data where I want to know how much I spent on
public transport in the last three years, grouped by the year:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;beancount&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;beancount&amp;gt; SELECT \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        year, SUM(number) AS total \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        WHERE account ~ &amp;#39;Expenses:PublicTransport&amp;#39; AND year &amp;gt;= 2019 \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        GROUP BY year&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;year total &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;---- ------&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2019 672.60&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2020 328.02&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2021  30.50&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The first column in the output contains the year and the second one contains the
total amount of money that I spent buying public transport tickets.&lt;&#x2F;p&gt;
&lt;p&gt;Side note: I&#x27;ve been working remote since 2016, but it&#x27;s pretty clear from those
numbers when the COVID-lockdown hit my town the most.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;2-fava&quot;&gt;2. Fava&lt;&#x2F;h3&gt;
&lt;p&gt;Fava is a community-maintained web interface for Beancount. At its core it&#x27;s a
Flask application which reads your &lt;code&gt;.beancount&lt;&#x2F;code&gt; file and provides all sorts of
visualizations you&#x27;ll find useful.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s a screenshot of one of the reports that Fava can generate for you:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;beancount.github.io&#x2F;fava&#x2F;&quot;&gt;&lt;img src=&quot;https:&#x2F;&#x2F;sgoel.dev&#x2F;posts&#x2F;how-you-can-track-your-personal-finances-using-python&#x2F;beancount-fava.png&quot; alt=&quot;Beancount Fava&quot; &#x2F;&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;There are multiple reports built-in, for instance balance sheet, income
statement, document browser (another neat feature of Beancount where you can
attach documents (eg. invoices) to transactions), and more. &lt;em&gt;And&lt;&#x2F;em&gt; you can
filter all the data using timeframes.&lt;&#x2F;p&gt;
&lt;p&gt;There is an online demo at this URL in case you&#x27;d like to get a feel for it:
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;fava.pythonanywhere.com&#x2F;&quot;&gt;https:&#x2F;&#x2F;fava.pythonanywhere.com&#x2F;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Between Fava and &lt;code&gt;bean-query&lt;&#x2F;code&gt;, I use Fava the most. I feel that the
visualizations provided by Fava give me a really good sense of almost all the
financial insights I&#x27;m looking for.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;That was it! Congratulations for making it this far! I hope this article was
able to interest you in this privacy-focused way of tracking personal finances.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re interested in learning more, you should consider buying an ebook I
wrote on this topic titled &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;personalfinancespython.com&quot;&gt;Tracking Personal Finances using Python&lt;&#x2F;a&gt;.  In this
ebook, I explain the concepts mentioned above in detail to help you build your
own personalized multi-banking application using Python.&lt;&#x2F;p&gt;
&lt;p&gt;Beancount and Double Entry Accounting can be slightly confusing to folks who are
just starting out. So this ebook is my attempt at lowering the barrier to entry.&lt;&#x2F;p&gt;
&lt;p&gt;And if you have questions about any of this, feel free to reach out to me on
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;siddhantgoel&quot;&gt;Twitter&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>How to debug better</title>
        <published>2021-07-22T00:00:00+00:00</published>
        <updated>2021-07-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/how-to-debug-better/"/>
        <id>https://sgoel.dev/posts/how-to-debug-better/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/how-to-debug-better/">&lt;p&gt;The very act of writing software implies that we (unintentionally) introduce
bugs. It&#x27;s possible to reduce the number of bugs you introduce. But it&#x27;s
definitely not possible to bring it down to zero. This means that most software
we work with build or use will fail, at times in interesting ways. And then you
the software developer would have to go in and figure out where things are going
wrong.&lt;&#x2F;p&gt;
&lt;p&gt;After spending a few years building, breaking, and then fixing software, I&#x27;ve
found a couple of things to keep in mind that help me when I&#x27;m trying to debug
something. They work on all kinds of bugs - from broken libraries where there&#x27;s
a difference between documented and actual behavior, to distributed systems
where touching one component affects a different one in ways that one would
normally not expect.&lt;&#x2F;p&gt;
&lt;p&gt;In this article, I&#x27;d like to describe some of these approaches that I&#x27;ve found
useful in discovering the root causes of issues in production and how to resolve
them.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;1-read-and-understand-what-s-happening&quot;&gt;1. Read and understand what&#x27;s happening&lt;&#x2F;h2&gt;
&lt;p&gt;The first approach is also the most obvious one.&lt;&#x2F;p&gt;
&lt;p&gt;We need to read, and more importantly, understand any and all the information
available to us. And when I use the word &quot;understand&quot;, I mean &lt;em&gt;really&lt;&#x2F;em&gt; give it a
long and hard look and try to get to the bottom of it.&lt;&#x2F;p&gt;
&lt;p&gt;When you&#x27;re trying to fix something that&#x27;s broken, it&#x27;s critical to build a
mental model of what the expected behavior is and what is happening instead.
Once you have that in place, reading and understanding the information available
to you (in the form of an error message or a log entry, or whatever you may have
at hand) goes a long way in figuring out what exactly is going wrong.&lt;&#x2F;p&gt;
&lt;p&gt;Often, I talk to engineers who are stuck with some exception in their code,
unable to make progress. Almost in every single case I&#x27;ve noticed that they tend
to ignore reading the stack trace that they are looking at. The general practice
is to put the entire exception inside the Google search box, with the hope of
finding a StackOverflow response with the exact same exception. At times this
works. When it doesn&#x27;t though, it&#x27;s pretty much a dead-end strategy of
debugging.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;2-think-using-first-principles&quot;&gt;2. Think using first principles&lt;&#x2F;h2&gt;
&lt;p&gt;Software is &lt;em&gt;generally&lt;&#x2F;em&gt; pretty deterministic (note the emphasis on the word
&lt;em&gt;generally&lt;&#x2F;em&gt;).&lt;&#x2F;p&gt;
&lt;p&gt;Things tend to have a well-defined flow. There are obviously huge exceptions to
this rule. But if your work involves developing user-facing products on the web
(like mine), you&#x27;re most likely working at an abstraction level where the flow
tends to be pretty well-defined.&lt;&#x2F;p&gt;
&lt;p&gt;As an example, a SaaS product generally has a frontend component, a backend
component and then the infrastructure that the whole thing is running on. There
are other things as well. For instance you likely have a load balancer layer
that all user-traffic goes through before hitting the frontend.  Or you might
have some queues which the backend is using to perform some background
processing.&lt;&#x2F;p&gt;
&lt;p&gt;Each of those components has a specific purpose in this chain. If, for instance
you&#x27;re facing a bug that spans across multiple layers, it&#x27;s generally helpful to
think from first principles as to what each component is supposed to do and
what it might be doing instead.&lt;&#x2F;p&gt;
&lt;p&gt;For instance, if you notice that the data the backend is receiving is not
something that it&#x27;s supposed to, it&#x27;ll help to build a mental model of the
complete user request - what the frontend sends to the backend, if the load
balancer can modify the request headers in some way, stuff like that.
Visualizing the stack like this goes a long way in being able to pinpoint the
problematic layer(s).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;3-avoid-trying-out-random-things&quot;&gt;3. Avoid trying out random things&lt;&#x2F;h2&gt;
&lt;p&gt;One of the rather interesting things I&#x27;ve seen software developers (including
me) do when trying to fix a problem is trying out &lt;em&gt;random&lt;&#x2F;em&gt; things. I will try
to illustrate this with a very basic example. Consider the following function:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; add&lt;&#x2F;span&gt;&lt;span&gt;(a, b):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; b&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We have a function called &lt;code&gt;add&lt;&#x2F;code&gt; which is supposed to add both its arguments
and return the result. Instead, the function returns the subtraction as a
result. What&#x27;s I&#x27;ve seen folks often do is to try out random approaches. One
example would be changing the definition to something like:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; add&lt;&#x2F;span&gt;&lt;span&gt;(a, b):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; b&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;... in the hopes that the final result will be what we expect.&lt;&#x2F;p&gt;
&lt;p&gt;This urge is natural. And at times this may certainly work. But it&#x27;s a bit like
throwing spaghetti at the wall. It&#x27;s not particularly efficient. It&#x27;s much
better to spend a quiet moment with the code at hand and try to get to the
bottom of it. The success rate will be much higher that way.&lt;&#x2F;p&gt;
&lt;p&gt;Again, I&#x27;ve been guilty of this myself as well. There are times when in a
desperate attempt to fix things, I try out solutions that are more or less
random relative to the problem at hand. These are definitely times when the &quot;hey
I wonder what happens if I do &lt;em&gt;this&lt;&#x2F;em&gt;&quot; thought process kicks the rational part of
the brain out. Over time though, I&#x27;ve noticed that this is not particularly
effective. So now whenever I feel this urge coming up, I try to keep it in check
and start by really trying to understand what&#x27;s wrong.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;4-ask-someone-else&quot;&gt;4. Ask someone else&lt;&#x2F;h2&gt;
&lt;p&gt;One of the best things to do is to ask someone! This could be a coworker, or
someone on the Internet in some online community you&#x27;re a part of.&lt;&#x2F;p&gt;
&lt;p&gt;Asking a coworker is definitely the more helpful option though, because
coworkers are likely to have more context on what you&#x27;re working on. Ideally
this is someone with more experience than you. Senior Engineers have ran into
enough problems over their careers. And over time they develop an ability to
pattern-match problems. So they might look at your bug which might remind them
of something different they were working on a few years ago (if not the same).
Having someone give you this perspective is invaluable and can go a long way in
solving the problem at hand.&lt;&#x2F;p&gt;
&lt;p&gt;Of course, this is not to say that only senior folks can help. Good engineers,
regardless of whether they&#x27;re junior or senior, tend to ask very interesting
questions. They might help you see the problem from a different angle, or follow
a line of questioning that you weren&#x27;t even considering earlier. Such
conversations are super helpful.&lt;&#x2F;p&gt;
&lt;p&gt;And if you don&#x27;t have any of those available, you can always try talking to a
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Rubber_duck_debugging&quot;&gt;rubber duck&lt;&#x2F;a&gt;. The first time I tried it out, I was extremely surprised at how
effective it was.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;5-step-out-and-take-a-walk&quot;&gt;5. Step out and take a walk&lt;&#x2F;h2&gt;
&lt;p&gt;If you&#x27;ve done all of the above and still don&#x27;t know how to proceed, the best
way forward might just be to take a break. Sometimes our brains have had enough.
When you reach that point, there&#x27;s really no way that you can consume and&#x2F;or
process more information.&lt;&#x2F;p&gt;
&lt;p&gt;In such situations, just stop working. Get away from your desk, ideally go out
and get some fresh air. Go for a run. Jump in the shower. Anything that keeps
you away from the problem at hand so that your body gets the break it needs.&lt;&#x2F;p&gt;
&lt;p&gt;You may feel guilty about not &quot;working&quot; when there are problems to solve. But
what&#x27;s really happening is that you&#x27;re letting your brain work in the
background. So technically speaking, you&#x27;re still making progress. And sooner or
later, the answer will hit you out of nowhere.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Uploading binary wheels to PyPI from Github Actions</title>
        <published>2020-11-03T00:00:00+00:00</published>
        <updated>2020-11-03T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/uploading-binary-wheels-to-pypi-from-github-actions/"/>
        <id>https://sgoel.dev/posts/uploading-binary-wheels-to-pypi-from-github-actions/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/uploading-binary-wheels-to-pypi-from-github-actions/">&lt;p&gt;If you write software using Python and your code has third-party dependencies,
you&#x27;ve probably heard of &quot;wheels&quot; before.&lt;&#x2F;p&gt;
&lt;p&gt;Wheels are the current standard of distributing Python packages and are intended
to replace eggs. Wheels are part of what makes the magic happen when you &lt;code&gt;pip install&lt;&#x2F;code&gt; something.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;background&quot;&gt;Background&lt;&#x2F;h2&gt;
&lt;p&gt;Let&#x27;s say you want to install Flask to build a web application. One of the steps
fairly early on in your development cycle is going to be &lt;code&gt;pip install Flask&lt;&#x2F;code&gt;.
When you run &lt;code&gt;pip install Flask&lt;&#x2F;code&gt;, &lt;code&gt;pip&lt;&#x2F;code&gt; queries the Python Packages Index (more
commonly known as &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pypi.org&quot;&gt;PyPI&lt;&#x2F;a&gt;) and fetches the Flask package to install it for you
locally.&lt;&#x2F;p&gt;
&lt;p&gt;The file format that &lt;code&gt;pip&lt;&#x2F;code&gt; downloads is what we&#x27;re going to talk about in this
post. Historically, this has been eggs (because you know, snakes lay eggs).
These days though, this format tends to be either a source distribution (which
is basically the raw source code of the package) or a &quot;wheel&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;d like to get a deeper understanding of what wheels actually look like at
a file-level, I would recommend reading &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.python.org&#x2F;dev&#x2F;peps&#x2F;pep-0427&#x2F;&quot;&gt;PEP 427&lt;&#x2F;a&gt;. In this post though, we&#x27;ll
focus on &lt;strong&gt;how&lt;&#x2F;strong&gt; to build wheels. Specifically, how to build binary wheels on
Linux, macOS, and Windows for Python packages that contain C extensions.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-problem&quot;&gt;The Problem&lt;&#x2F;h2&gt;
&lt;p&gt;I maintain a package called &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;streaming-form-data&quot;&gt;streaming-form-data&lt;&#x2F;a&gt; which allows you to parse
&lt;code&gt;multipart&#x2F;form-data&lt;&#x2F;code&gt; encoded byte chunks. Due to the nature of this package,
the performance-critical parts were written using Cython. So whenever I wanted
to create a new release on PyPI, I would compile the Cython code to a C
extension and include that in the final distribution.&lt;&#x2F;p&gt;
&lt;p&gt;Long story short, this package contains a C extension.&lt;&#x2F;p&gt;
&lt;p&gt;So far, I had just been uploading source distributions to PyPI. This meant that
the end-user was responsible for ensuring that they have a working C compiler
(something like GCC) on their system. It mostly worked, until recently when
someone reported that they found it a hassle to install it on Windows because
the compiler they were using had some issues with the generated C code (or the
other way around, one could argue).&lt;&#x2F;p&gt;
&lt;p&gt;At that point I thought about looking into generating platform-specific binary
wheels for this package and uploading those to PyPI alongside the source
distribution. This meant generating wheels for Linux, macOS, and Windows which
users could install directly on their machines without having to compile
anything. The advantage for users was huge, in that they didn&#x27;t have to worry
about system-level dependencies at all.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;building-platform-wheels&quot;&gt;Building platform wheels&lt;&#x2F;h2&gt;
&lt;p&gt;This is where things got slightly tricky. To generate a wheel for a given
platform, you need to have access to that platform. I run &lt;a href=&quot;https:&#x2F;&#x2F;sgoel.dev&#x2F;posts&#x2F;the-switch-from-arch-to-debian&#x2F;&quot;&gt;Linux&lt;&#x2F;a&gt; on my private
laptop, which meant I could only generate binary wheels which would work on
Linux boxes but not on macOS or Windows. In theory, I guess there &lt;strong&gt;might&lt;&#x2F;strong&gt;
have been a possibility of generating these wheels for other platforms using
virtual machines. But that was a &lt;strong&gt;huge&lt;&#x2F;strong&gt; &quot;might&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;Luckily, I discovered that &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;actions&quot;&gt;Github Actions&lt;&#x2F;a&gt; actually supports &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.github.com&#x2F;en&#x2F;free-pro-team@latest&#x2F;actions&#x2F;reference&#x2F;specifications-for-github-hosted-runners&quot;&gt;macOS and
Windows&lt;&#x2F;a&gt; runners, which made the whole thing so much more convenient.&lt;&#x2F;p&gt;
&lt;p&gt;I was already using Github Actions for continuous testing. So building platform
wheels was a matter of adding another workflow to the CI setup.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;roadblock-2&quot;&gt;Roadblock #2&lt;&#x2F;h2&gt;
&lt;p&gt;My first attempt was to build the platform wheels by using the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pypi.org&#x2F;project&#x2F;wheel&#x2F;&quot;&gt;wheel&lt;&#x2F;a&gt; package.
If you have &lt;code&gt;wheel&lt;&#x2F;code&gt; installed, you&#x27;ll have a &lt;code&gt;bdist_wheel&lt;&#x2F;code&gt; command available
under &lt;code&gt;python setup.py&lt;&#x2F;code&gt;. Going that route, the Actions workflow definition
looked something like the following:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;build_wheels&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;  runs-on&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; ${{ matrix.os }}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;  strategy&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;    matrix&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;      os&lt;&#x2F;span&gt;&lt;span&gt;: [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;ubuntu-latest&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; macos-latest&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; windows-latest&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;  steps&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt; uses&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; actions&#x2F;checkout@v2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt; uses&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; actions&#x2F;setup-python@v2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;      with&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;        python-version&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 3.8&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; Setup pip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;      run&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;         python -m pip install --upgrade pip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;         python -m pip install wheel&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; Build wheel&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;      run&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; python setup.py bdist_wheel&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt; uses&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; actions&#x2F;upload-artifact@v2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;      with&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;        path&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; .&#x2F;dist&#x2F;*.whl&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This would&#x27;ve been it, except that it only worked for building wheels on macOS
and Windows, but failed on Linux. The reason was that building binary wheels on
Linux is further complicated by the fact that there are hundreds (if not
thousands) of Linux distributions in existence, each treating system packages
differently.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;pypa&#x2F;manylinux&quot;&gt;manylinux&lt;&#x2F;a&gt; project from the PyPA that aims to make this easier. But
running it directly still feels (to me) a bit of a hassle.&lt;&#x2F;p&gt;
&lt;p&gt;Add to this the fact that building binary wheels would have to take into account
multiple CPU architectures, building packages for different Python versions
across multiple operating systems. The solution described above would&#x27;ve
required much more work to be &quot;production ready&quot;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-final-solution&quot;&gt;The Final Solution&lt;&#x2F;h2&gt;
&lt;p&gt;Luckily, I came across &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;cibuildwheel.readthedocs.io&#x2F;en&#x2F;stable&#x2F;&quot;&gt;cibuildwheel&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;cibuildwheel&lt;&#x2F;code&gt; is an amazing project that helps with exactly this problem. It&#x27;s
a Python package that&#x27;s meant to run on your CI server (Github Actions in this
case) and abstracts away all the details of building binary wheels for multiple
platforms for different CPU architectures.&lt;&#x2F;p&gt;
&lt;p&gt;So instead of relying on &lt;code&gt;python setup.py bdist_wheel&lt;&#x2F;code&gt;, you use &lt;code&gt;cibuildwheel&lt;&#x2F;code&gt;
instead to build the wheels. Since this looked really promising, I modified the
&quot;Build wheel&quot; section in the previous snippet to look like the following:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;build_wheels&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;  runs-on&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; ${{ matrix.os }}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;  strategy&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;    matrix&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;      os&lt;&#x2F;span&gt;&lt;span&gt;: [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;ubuntu-latest&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; macos-latest&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; windows-latest&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;  steps&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt; uses&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; actions&#x2F;checkout@v2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt; uses&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; actions&#x2F;setup-python@v2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;      with&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;        python-version&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 3.8&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; Setup pip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;      run&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;         python -m pip install --upgrade pip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;         python -m pip install cibuildwheel==1.6.4&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; Build wheel&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;      run&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; python -m cibuildwheel --output-dir dist&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;      env&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;        CIBW_BUILD&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; cp36-* cp37-* cp38-*&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt; uses&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; actions&#x2F;upload-artifact@v2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;      with&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;        path&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; .&#x2F;dist&#x2F;*.whl&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So basically just replacing the &lt;code&gt;python setup.py bdist_wheel&lt;&#x2F;code&gt; step with
&lt;code&gt;cibuildwheel&lt;&#x2F;code&gt;, which made the workflow go through without a hitch. And at the
end of the pipeline, there were 12 binary wheels available for me to upload to
PyPI.&lt;&#x2F;p&gt;
&lt;p&gt;Always love it when software &quot;just works&quot;. 😅&lt;&#x2F;p&gt;
&lt;p&gt;The only modification I had to make was specifying a value for the &lt;code&gt;CIBW_BUILD&lt;&#x2F;code&gt;
environment variable to restrict the Python versions. For this specific case, I
limited the Python versions to 3.6, 3.7, and 3.8. Other than that though, I left
everything in &lt;code&gt;cibuildwheel&lt;&#x2F;code&gt; to their default values.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;Python being an interpreted language, if any part of your source code needs
compilation, things get slightly tricky if you want others to be able to install
your packages easily.&lt;&#x2F;p&gt;
&lt;p&gt;Binary wheels are a huge step in that direction, but the underlying complexity
of the problem they&#x27;re trying to solve means that the user experience of
building wheels for different platforms is often not 100% smooth.&lt;&#x2F;p&gt;
&lt;p&gt;IMHO though, &lt;code&gt;cibuildwheel&lt;&#x2F;code&gt; eliminates much of that complexity for you, the
packager. The solution I described earlier works pretty well for me, and I would
highly recommend using it in your own projects if there&#x27;s the need.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>The Almanack of Naval Ravikant</title>
        <published>2020-10-24T00:00:00+00:00</published>
        <updated>2020-10-24T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/the-almanack-of-naval-ravikant/"/>
        <id>https://sgoel.dev/posts/the-almanack-of-naval-ravikant/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/the-almanack-of-naval-ravikant/">&lt;p&gt;If you&#x27;re even remotely into reading books on self-improvement or &quot;modern&quot;
philosophy, &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.navalmanack.com&#x2F;&quot;&gt;The Almanack of Naval Ravikant&lt;&#x2F;a&gt; is the one book you absolutely must
consider reading.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s available online for free. Physical copies cost a little bit of money
(obviously). But given the value that I feel is inside the book, the price is
&lt;strong&gt;extremely&lt;&#x2F;strong&gt; reasonable.&lt;&#x2F;p&gt;
&lt;p&gt;I first learned about Naval from his interview on the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;tim.blog&#x2F;2015&#x2F;08&#x2F;18&#x2F;the-evolutionary-angel-naval-ravikant&#x2F;&quot;&gt;Tim Ferris podcast&lt;&#x2F;a&gt;. It&#x27;s
always a treat to listen to someone who looks at things from a first-principles
point of view. Plus he has this rare ability to express a ton of insight using
an easy to understand language. In the sense that when you&#x27;ve listened to what
he&#x27;s saying or have read one of his blog posts or tweets, sometimes you go &quot;that
makes &lt;em&gt;too much&lt;&#x2F;em&gt; sense&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;So when I came to know that this book is available on Amazon, I ordered it right
away. And already a few pages into the book, I had the feeling that this is
going to be one of my all-time favorite books. Mostly centered as a guide to
wealth and happiness, the contents of this book cover a lot of ground. At no
point though does that impact the core ideas that this book is trying to convey.
There really are a ton of things to learn from in this book.&lt;&#x2F;p&gt;
&lt;p&gt;Content-wise, the book comprises of some of Naval&#x27;s best tweets (and the
&quot;tweetstorms&quot;), essays and interviews. The flow from one topic to the next is
very fluid and you should easily be able to finish it in a day or two. But even
though it&#x27;s an easy read, you&#x27;ll most certainly find that this book requires
multiple readings to be able to fully grasp and internalise the ideas expressed.
Like how it is with most classics.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;&#x2F;strong&gt;: read it!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Continuous Deployment with Netlify and Zola</title>
        <published>2020-09-13T00:00:00+00:00</published>
        <updated>2020-09-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/continuous-deployment-with-netlify-and-zola/"/>
        <id>https://sgoel.dev/posts/continuous-deployment-with-netlify-and-zola/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/continuous-deployment-with-netlify-and-zola/">&lt;p&gt;I spent some time last week enabling continous deployment for this site. As a
quick bit of background, I use the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.getzola.org&quot;&gt;Zola&lt;&#x2F;a&gt; static site generator to generate the
site and &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.netlify.com&quot;&gt;Netlify&lt;&#x2F;a&gt; to host it.&lt;&#x2F;p&gt;
&lt;p&gt;I can highly recommend both the tools, as Zola is a very simple and fast
generator and Netlify is a pretty feature-complete static hosting platform with
an &lt;strong&gt;extremely&lt;&#x2F;strong&gt; generous free tier. And as we&#x27;ll later see, the two work quite
well together.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;The basic idea is that Netlify allows you to connect your sites to Git
repositories. At the time of this writing, this includes Github, Gitlab, and
Bitbucket. Once a repository has been connected to a site, every time you push
to the &quot;main&quot; development branch, Netlify will trigger a deployment pipeline.&lt;&#x2F;p&gt;
&lt;p&gt;This workflow can either be configured from within their web interface or using
a &lt;code&gt;netlify.toml&lt;&#x2F;code&gt; config file in the root folder of your repository. For static
sites using Zola, this file looks like the following.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;build&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;publish =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;public&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;command =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;zola build&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;build&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;environment&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ZOLA_VERSION =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;0.12.0&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The most important line of code in this config is setting &lt;code&gt;ZOLA_VERSION&lt;&#x2F;code&gt;. This
tells Netlify that we&#x27;re working with a Zola site so it should please ensure
that the &lt;code&gt;zola&lt;&#x2F;code&gt; binary exists, corresponds to version &lt;code&gt;0.12.0&lt;&#x2F;code&gt; and is available
on &lt;code&gt;$PATH&lt;&#x2F;code&gt;. The other two lines of config tell Netlify that on every push to
&lt;code&gt;master&lt;&#x2F;code&gt; (or whatever your main development branch is called), the command &lt;code&gt;zola build&lt;&#x2F;code&gt; should be run and the contents of the &lt;code&gt;public&lt;&#x2F;code&gt; folder (which &lt;code&gt;zola build&lt;&#x2F;code&gt;
populates) should be published to the static site.&lt;&#x2F;p&gt;
&lt;p&gt;Once this file is in place and your Git repository is connected to your Netlify
site, the process basically just works. That&#x27;s basically the only switch you
would have to flip.&lt;&#x2F;p&gt;
&lt;p&gt;Overall this process was quite straight forward and I&#x27;m really happy with the
result. The nicest thing is that the one manual step involved with this site (of
uploading to Netlify) is not there anymore. Prior to having this pipeline in
place, I was using &lt;code&gt;netlify-cli&lt;&#x2F;code&gt; every time I made a change to deploy the site.
Additionally there was also the dance of local git branches to be able to
differentiate between what&#x27;s live (the &lt;code&gt;stable&lt;&#x2F;code&gt; branch) and what&#x27;s not (the
&lt;code&gt;master&lt;&#x2F;code&gt; branch).&lt;&#x2F;p&gt;
&lt;p&gt;Now, I can just make some edits and push to Github and the end result is
automatically published to &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;sgoel.dev&quot;&gt;https:&#x2F;&#x2F;sgoel.dev&lt;&#x2F;a&gt; within a few seconds. Netlify
even sends me a nice little email everytime the site contents change. I don&#x27;t
have to perform any extra steps locally, and there&#x27;s only one git branch to
maintain. And all the stuff that&#x27;s pushed to Github is &quot;live&quot;, and everything
else is a draft.&lt;&#x2F;p&gt;
&lt;p&gt;💯&#x2F;💯 would recommend.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Multiple Mixins (and naming conflicts) in Python</title>
        <published>2020-06-28T00:00:00+00:00</published>
        <updated>2020-06-28T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/multiple-mixins-and-naming-conflicts-in-python/"/>
        <id>https://sgoel.dev/posts/multiple-mixins-and-naming-conflicts-in-python/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/multiple-mixins-and-naming-conflicts-in-python/">&lt;p&gt;Mixins are quite useful. In case you&#x27;re not familiar with the idea, it&#x27;s a step
in the direction of &quot;composition over inheritance&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;In Object Oriented Programming, consider that you&#x27;re implementing a class and
you want a bunch of functions to be available inside of that class. The idea
behind mixins is to implement these functions in other independent classes and
inherit your actual class from these utility classes. Such utility classes are
called &quot;mixins&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;In Python, for instance, this is possible due to &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;realpython.com&#x2F;lessons&#x2F;multiple-inheritance-python&#x2F;&quot;&gt;multiple inheritance&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;As an example, assume that we want to implement an &lt;code&gt;Animal&lt;&#x2F;code&gt; class, the objects
of which should have the capability to both &quot;bark&quot; and &quot;meow&quot;. Putting
biological concerns aside, this can be implemented by defining the &quot;barking&quot;
functionality inside a &lt;code&gt;Barker&lt;&#x2F;code&gt; class and the &quot;meowing&quot; functionality inside a
&lt;code&gt;Meower&lt;&#x2F;code&gt; class. You can then inherit the &lt;code&gt;Animal&lt;&#x2F;code&gt; class from both &lt;code&gt;Barker&lt;&#x2F;code&gt; and
&lt;code&gt;Meower&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Barker&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; bark&lt;&#x2F;span&gt;&lt;span&gt;(self):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;Woof!&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Meower&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; meow&lt;&#x2F;span&gt;&lt;span&gt;(self):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;Meow!&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Animal&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Barker&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Meower&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; greet&lt;&#x2F;span&gt;&lt;span&gt;(self):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        self&lt;&#x2F;span&gt;&lt;span&gt;.bark()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        self&lt;&#x2F;span&gt;&lt;span&gt;.meow()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This has the advantage that if later on we want to add another &lt;code&gt;Animal&lt;&#x2F;code&gt;-ish
class which wants the ability to &quot;bark&quot;, we simply have to choose &lt;code&gt;Barker&lt;&#x2F;code&gt; as
one of the base classes to have the &lt;code&gt;bark()&lt;&#x2F;code&gt; function available in the new
class.&lt;&#x2F;p&gt;
&lt;p&gt;While mixins come in super handy when solving real-world problems, they often
come with their own set of issues. And some of these issues come from how the
programming language you&#x27;re using implements multiple inheritance.&lt;&#x2F;p&gt;
&lt;p&gt;In the Python world, one such problem is — what happens if two different mixins
define the same function?&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s take a real-world-ish problem to illustrate the issue.&lt;&#x2F;p&gt;
&lt;p&gt;Consider that we&#x27;re writing a web application serving HTTP requests. At the end
of every web request we would like to perform some cleanup related to the
database and the cache. Let&#x27;s also assume that our web framework of choice
provides an &lt;code&gt;on_finish&lt;&#x2F;code&gt; hook on individual requests to place such cleanup code.&lt;&#x2F;p&gt;
&lt;p&gt;When implementing something like that using mixins, this is how it could roughly
look like.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; DatabaseCleaner&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; on_finish&lt;&#x2F;span&gt;&lt;span&gt;(self):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;Cleaning the database&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; CacheCleaner&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; on_finish&lt;&#x2F;span&gt;&lt;span&gt;(self):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;Cleaning the cache&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Request&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;DatabaseCleaner&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; CacheCleaner&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    pass&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For this code snippet, what do you think the output would be?&lt;&#x2F;p&gt;
&lt;p&gt;Because of the way Python implements multiple inheritance, it ends up calling
the function defined in the &lt;strong&gt;first&lt;&#x2F;strong&gt; base class. In our example, this happens
to be &lt;code&gt;DatabaseCleaner.on_finish&lt;&#x2F;code&gt;, which is what ends up being called. This has
the unfortunate side-effect that &lt;code&gt;CacheCleaner.on_finish&lt;&#x2F;code&gt; never gets called.&lt;&#x2F;p&gt;
&lt;p&gt;This is obviously not what we want. We want to print both &quot;Cleaning the
database&quot; as well as &quot;Cleaning the cache&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;One solution for this problem is to name those &lt;code&gt;on_finish&lt;&#x2F;code&gt; functions
differently. So that &lt;code&gt;DatabaseCleaner&lt;&#x2F;code&gt; implements &lt;code&gt;on_finish_database&lt;&#x2F;code&gt; and
&lt;code&gt;CacheCleaner&lt;&#x2F;code&gt; implements &lt;code&gt;on_finish_cache&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;While that could work (with a little bit of extra effort), it sounds a little
odd.  What if those two classes are written by two completely different authors
who don&#x27;t know about the existence of each other? Not to mention that this now
puts the responsibility on the calling code to remember to call those
&lt;code&gt;on_finish_*&lt;&#x2F;code&gt; functions, defeating the mixin-magic in the first place.&lt;&#x2F;p&gt;
&lt;p&gt;Luckily, Python provides a way to call the &quot;next in line&quot; function, which refers
to the function with the same name in the &quot;next&quot; base class. Let&#x27;s modify the
&lt;code&gt;clean()&lt;&#x2F;code&gt; functions in both the mixins to something like the following.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; DatabaseCleaner&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; clean&lt;&#x2F;span&gt;&lt;span&gt;(self):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;Cleaning the database&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        try&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            next_clean&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; super&lt;&#x2F;span&gt;&lt;span&gt;().clean()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        except&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AttributeError&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;            pass&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        else&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            next_clean()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Here, inside the &lt;code&gt;try&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;except&lt;&#x2F;code&gt; clause, we first try to find the
function that&#x27;s &quot;next in line&quot; by using &lt;code&gt;super()&lt;&#x2F;code&gt;. In case one was found, we
simply call it. In case no such function was found (which most likely means that
this is the last base class), then &lt;code&gt;super().clean()&lt;&#x2F;code&gt; would raise an
&lt;code&gt;AttributeError&lt;&#x2F;code&gt;, in which case we can choose to do nothing and do a clean exit.&lt;&#x2F;p&gt;
&lt;p&gt;While this does add 6 extra lines to the mixin code, I feel it&#x27;s a small price
to pay. As library authors, it&#x27;s nice to act as good citizens and keep in mind
that there are other libraries out there as well which our users might use which
may result in name clashes. I feel it&#x27;s a good idea to make our code resistant
to such things.&lt;&#x2F;p&gt;
&lt;p&gt;I came across this problem when writing &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;tornado-sqlalchemy&quot;&gt;tornado-sqlalchemy&lt;&#x2F;a&gt;, which is a Python
package that provides SQLAlchemy integration for Tornado projects. This
integration is provided using a &lt;code&gt;DatabaseMixin&lt;&#x2F;code&gt; which makes database &lt;code&gt;Session&lt;&#x2F;code&gt;
objects available in the request classes.&lt;&#x2F;p&gt;
&lt;p&gt;When using this library in a work project which also had &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;sentry.io&quot;&gt;Sentry&lt;&#x2F;a&gt; integration
enabled through &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;getsentry&#x2F;sentry-python&quot;&gt;sentry-python&lt;&#x2F;a&gt;, I noticed that both &lt;code&gt;tornado-sqlalchemy&lt;&#x2F;code&gt; and
&lt;code&gt;sentry-python&lt;&#x2F;code&gt; provided (at least at that time) mixins to perform cleanup at the
end of the request. &lt;code&gt;tornado-sqlalchemy&lt;&#x2F;code&gt; did the database cleanup while
&lt;code&gt;sentry-python&lt;&#x2F;code&gt; did some cleanup related to Sentry.&lt;&#x2F;p&gt;
&lt;p&gt;But since Tornado provides only a single cleanup function called &lt;code&gt;on_finish&lt;&#x2F;code&gt;,
the same function was being overridden by both the libraries. This resulted in
the fact that if a user was using both the mixins in their application code,
depending on which class order they used when defining the (multiple)
inheritance, one cleanup function would be skipped completely.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve since patched the &lt;code&gt;DatabaseMixin&lt;&#x2F;code&gt; provided by &lt;code&gt;tornado-sqlalchemy&lt;&#x2F;code&gt; so that
this problem doesn&#x27;t exist anymore. But it was still interesting to run into
this issue and find the solution.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Book Review: The Phoenix Project</title>
        <published>2020-04-15T00:00:00+00:00</published>
        <updated>2020-04-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/book-review-the-phoenix-project/"/>
        <id>https://sgoel.dev/posts/book-review-the-phoenix-project/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/book-review-the-phoenix-project/">&lt;p&gt;Every now and then I like to look for book recommendations on places like Hacker
News and Lobsters where the audience is primarily technical. The one book that
comes up consistently in these recommendations is &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.goodreads.com&#x2F;book&#x2F;show&#x2F;17255186-the-phoenix-project&quot;&gt;The Phoenix Project&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;So last week I decided to order it and check for myself what the fuss was all
about. Luckily it arrived at my doorstep right before the Easter weekend, so I
had the entire weekend to read it.&lt;&#x2F;p&gt;
&lt;p&gt;I managed to finish reading over the weekend, and I have to say that &lt;strong&gt;this is
easily one of the best books I&#x27;ve ever read&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;“If you don’t like to read, you haven’t found the right book.” – &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.goodreads.com&#x2F;quotes&#x2F;483565-if-you-don-t-like-to-read-you-haven-t-found-the&quot;&gt;J. K. Rowling&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;background&quot;&gt;Background&lt;&#x2F;h2&gt;
&lt;p&gt;The book is set inside a fictitious company called Parts Unlimited. It&#x27;s
narrated from the point of view of Bill, an IT manager. The company is losing
customers and money, the board is really unhappy with the performance, and the
internal business processes are dysfunctional at best.&lt;&#x2F;p&gt;
&lt;p&gt;In this setting, Bill is suddenly promoted to being the VP of a
less-than-optimal IT department and asked to deliver on the so-called Phoenix
Project within 90 days, failing which the entire department shall be outsourced.&lt;&#x2F;p&gt;
&lt;p&gt;The story of how Bill tries to understand his new job description, identifying
and fixing the existing (broken) processes, the people he now has to work and
deal with, navigating the corporate hierarchy so he can get things done, and
even figuring out what needs to be done in the first place, is what the book is
all about.&lt;&#x2F;p&gt;
&lt;p&gt;While reading, I tended to focus on the gaps that development and IT departments
were facing. But as you read more, you find out that the business problems that
this company is facing are much deeper. One prime example is that the upper
management doesn&#x27;t consider software development &#x2F; IT as an integral part of the
business. This often leads to unreasonable expectations from other departments
that IT then has to deliver on, clashes between people and departments, lots of
blame games, so on and so forth.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;style&quot;&gt;Style&lt;&#x2F;h2&gt;
&lt;p&gt;I wasn&#x27;t exactly sure what to expect from a &quot;fiction novel about DevOps&quot;. DevOps
is probably one of the last subjects that comes to my mind when I think about
fiction. This book proved me wrong in so many ways.&lt;&#x2F;p&gt;
&lt;p&gt;The Phoenix Project is a very well-written book. The storyline is gripping. At
least for me it was difficult to put the book down. And if you&#x27;ve spent a lot of
time working in or with software &#x2F; IT departments, you&#x27;ll find that the
characters and situations in this book are quite familiar (if not relatable).
Finally, the message that this book is trying to highlight is delivered to the
reader slowly but consistently.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;should-you-read-it&quot;&gt;Should you read it?&lt;&#x2F;h2&gt;
&lt;p&gt;It depends on what your taste in books is like. I do believe that if you work
directly in software development &#x2F; IT or you interact with other folks who do,
this book is an absolute must-read.&lt;&#x2F;p&gt;
&lt;p&gt;More generally speaking, my recommendation depends on the kind of software
company you work at. In terms of the development &#x2F; IT split, I feel that there
can be two kinds here.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;companies where there is a clear distinction between development and IT&lt;&#x2F;li&gt;
&lt;li&gt;companies where DevOps is core to the software development culture&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;If you belong to the first group, this book is highly recommended. Speaking from
personal experience, I&#x27;ve had a chance in the past to work at a place where the
deployment culture was very much still developers &quot;throwing code over to the
wall&quot; to the operations people. If I had a chance to go back in time and make
everyone there read this book, I absolutely would. This &quot;development &#x2F;
deployment&quot; distinction affected my personal productivity quite a bit, and I
think having everyone read this book would&#x27;ve at least pushed things in the
right direction.&lt;&#x2F;p&gt;
&lt;p&gt;On the other hand, if you belong to the second group, you may not &lt;em&gt;need&lt;&#x2F;em&gt; to read
this book but it still could be a nice read. If you&#x27;ve worked at companies which
embrace DevOps as a core cultural value, you probably can&#x27;t imagine having it
any other way. So along those lines, this book can be a humorous tale about what
can happen when things aren&#x27;t structured that way.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;All in all, a wonderful read. Highly recommended!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Following news 24x7 considered harmful</title>
        <published>2020-04-13T00:00:00+00:00</published>
        <updated>2020-04-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/following-news-24x7-considered-harmful/"/>
        <id>https://sgoel.dev/posts/following-news-24x7-considered-harmful/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/following-news-24x7-considered-harmful/">&lt;p&gt;One of the most impactful changes I&#x27;ve made to my lifestyle is not following
news 24x7, either from TV channels or aggregator sites. Instead, I read one or
two credible and objective news outlets on the Internet once (or maximum twice)
a day to keep myself updated. But that&#x27;s about it.&lt;&#x2F;p&gt;
&lt;p&gt;This one practice has had a significantly positive impact on my mental
well-being, and I recommend everyone to try it out, unless your job depends
directly on consuming &#x2F; generating news.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;quest-for-content&quot;&gt;Quest for Content&lt;&#x2F;h2&gt;
&lt;p&gt;One of my biggest issues with &quot;always on&quot; news is the quest for content. If
you&#x27;re a content publisher on the Internet pushing content out on a regular to
semi-regular basis, you probably know what I&#x27;m talking about. You have to
generate new content &lt;em&gt;all the time&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Unless content generation comes naturally to you, there&#x27;s a significant effort
that goes in towards generating high-quality content. Not putting in that effort
leads to quality gradually declining. I&#x27;ve seen plenty of websites that started
out extremely focused, publishing very high-quality stuff. Eventually though,
they started running out of stories and started putting out irrelevant and
low-quality content.&lt;&#x2F;p&gt;
&lt;p&gt;I feel that some version of that applies to a couple of news media outlets as
well.&lt;&#x2F;p&gt;
&lt;p&gt;For instance, there are plenty of active news channels in India that started out
on a good note back in the late 90s &#x2F; early 2000s. But now in 2020 when I look
at the kind of stuff that they&#x27;re reporting, it&#x27;s beyond disappointing. Not only
are the stories irrelevant and sensationalist, most of them are extremely low
quality. Check the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;hashtag&#x2F;WhyIsIndianMediaStupid&quot;&gt;#WhyIsIndianMediaStupid&lt;&#x2F;a&gt; hashtag on Twitter for examples.&lt;&#x2F;p&gt;
&lt;p&gt;Apart from low quality content, the bigger problem I see is opinionated
reporting. News, in the true sense of the word, is supposed to mean objective
reporting of facts. Ideally it&#x27;s supposed to be black and white. This does not
seem to be the case anymore.&lt;&#x2F;p&gt;
&lt;p&gt;Now it&#x27;s completely normal for anchors to deliver news along with their own
opinions, that may potentially promote partisanship. Plus there are enough
outlets reporting non-factual stories (&quot;alternative facts&quot;, if you will). Not
only do I feel that this &quot;news -&amp;gt; opinion&quot; transition is a recipe for disaster,
it could also be threatening the very concept of democracy as we know it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;breaking-news&quot;&gt;Breaking News&lt;&#x2F;h2&gt;
&lt;p&gt;Most news outlets these days report stories as soon as they come in. A deeper
analysis of why this has become such a huge trend is beyond my understanding. I
do believe that there&#x27;s some amount of &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Fear_of_missing_out&quot;&gt;FOMO&lt;&#x2F;a&gt; (Fear Of Missing Out)
capitalization in there.&lt;&#x2F;p&gt;
&lt;p&gt;In any case, what I&#x27;ve noticed is that for someone like me, whose job does not
directly depend on news, the majority of those stories can wait. For me, it
doesn&#x27;t make a huge difference if I consume those stories right away, or
postpone reading them to the next day or day after.&lt;&#x2F;p&gt;
&lt;p&gt;Attaching the word &quot;breaking&quot; to a given piece of news creates a sense of
urgency in my mind. Whether the urgency is positive or negative depends on the
story. But as of April 2020 it&#x27;s difficult to find positive news stories, so
most of the times the urgency tends to be negative. This activates all the
anxiety muscles in my body (and I&#x27;m pretty sure they&#x27;re a thing), making me feel
restless, anxious, and completely distracted from what I was doing earlier.&lt;&#x2F;p&gt;
&lt;p&gt;On the other hand, if I don&#x27;t consume those stories immediately and schedule
reading them to a fixed part of the day instead, this helps at least with the
distraction. I&#x27;m probably still going to feel restless and anxious if the story
is negative. But if I restrict myself to reading the news in a given time slot
during the day, at least there wouldn&#x27;t be any productivity impact during the
rest of the day.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;anxiety&quot;&gt;Anxiety&lt;&#x2F;h2&gt;
&lt;p&gt;Like I mentioned earlier, unfortunately most of the news as of April 2020 is
negative. That&#x27;s just how the world seems to be running its course right now.&lt;&#x2F;p&gt;
&lt;p&gt;And as tiny cogs in this machine, there&#x27;s not much we can do &lt;em&gt;in the short
term&lt;&#x2F;em&gt; to turn the tide. There are definitely small steps that individuals can
take right now for change to become visible a few years down the line. But right
now, for example, apart from washing my hands and keeping distance from people I
don&#x27;t know, there&#x27;s not much else in my capacity to be able to change the
COVID-19 situation across the planet.&lt;&#x2F;p&gt;
&lt;p&gt;So it&#x27;s more or less a given that when you choose to follow negative news 24x7,
you&#x27;re essentially choosing to expose your mind to negative news constantly, all
day long. No wonder it&#x27;s going to push the anxiety levels high.&lt;&#x2F;p&gt;
&lt;p&gt;A core principle of &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;dailystoic.com&#x2F;anxiety&#x2F;&quot;&gt;Stoicism&lt;&#x2F;a&gt; is to not worry about what you can&#x27;t change. This
is much easier said than done, but it&#x27;s definitely not impossible. But while
you can attempt to train your mind along this line, why not instead solve the
problem at its core and reduce your constant exposure to 24x7 news altogether?&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;I guess one of the things it boils down to is consuming content on your own
schedule rather than on someone else&#x27;s. The news outlets you&#x27;re following,
excepting a few, are most likely driven by an advertising based business model,
which means they want to have as much of your attention as possible. While that
makes them money, it impacts your health.&lt;&#x2F;p&gt;
&lt;p&gt;Besides, extremely important news stories (one example would be official
statements from the local &#x2F; national governments) have a way of somehow reaching
you wherever you are. So reducing your news consumption frequency is most likely
not going to affect your daily life too much.&lt;&#x2F;p&gt;
&lt;p&gt;I would highly recommend trying this out. So far I&#x27;ve noticed only positive
effects, without impacting my general awareness of what&#x27;s going on around me.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>How to enable target=&quot;_blank&quot; links in Zola</title>
        <published>2020-04-07T00:00:00+00:00</published>
        <updated>2020-04-07T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/how-to-enable-target-blank-links-in-zola/"/>
        <id>https://sgoel.dev/posts/how-to-enable-target-blank-links-in-zola/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/how-to-enable-target-blank-links-in-zola/">&lt;p&gt;Zola is a nice static site generator. In fact, so nice that I moved this site
over from Pelican &lt;a href=&quot;https:&#x2F;&#x2F;sgoel.dev&#x2F;posts&#x2F;moving-from-pelican-to-zola&#x2F;&quot;&gt;a few weeks back&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The one thing I was missing though was the ability to set &lt;code&gt;target=&quot;_blank&quot;&lt;&#x2F;code&gt; on
links within the content. There is a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;getzola&#x2F;zola&#x2F;issues&#x2F;681&quot;&gt;relevant Github issue&lt;&#x2F;a&gt; from last year
which proposes this feature for Zola. But the general consensus from that thread
is that this functionality should ideally be supported by &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;raphlinus&#x2F;pulldown-cmark&quot;&gt;pulldown-cmark&lt;&#x2F;a&gt;,
which is the Markdown rendering library that Zola uses.&lt;&#x2F;p&gt;
&lt;p&gt;While I do agree with that decision, the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;raphlinus&#x2F;pulldown-cmark&#x2F;issues&#x2F;346&quot;&gt;relevant issue&lt;&#x2F;a&gt; from the
&lt;code&gt;pulldown-cmark&lt;&#x2F;code&gt; issue tracker looks a little discouraging. It is referenced by
a different issue titled &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;raphlinus&#x2F;pulldown-cmark&#x2F;issues&#x2F;350&quot;&gt;Extensible rendering prototype&lt;&#x2F;a&gt; which is currently
marked as closed with a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;raphlinus&#x2F;pulldown-cmark&#x2F;pull&#x2F;350#issuecomment-546729410&quot;&gt;comment&lt;&#x2F;a&gt; from the maintainer saying that it&#x27;s unlikely
for there to be much progress on this in the foreseeable future.&lt;&#x2F;p&gt;
&lt;p&gt;I wasn&#x27;t sure what to make of the whole thing, but I do understand how hard it
can be to prioritize open-source projects in between daily work and personal
life. And since my Rust skills at the moment are beginner-level (at best), I
tried to work my way around it using JavaScript instead.&lt;&#x2F;p&gt;
&lt;p&gt;The first step was to define a configuration option called &lt;code&gt;enable_target_blank&lt;&#x2F;code&gt;
to control this setting. Zola allows arbitrary user-defined configuration
options, so this was rather straight forward.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;[extra]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;enable_target_blank&lt;&#x2F;span&gt;&lt;span&gt; = true&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I then added the following JavaScript snippet in the base HTML template.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;var&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; enableTargetBlank&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; = function&lt;&#x2F;span&gt;&lt;span&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    var&lt;&#x2F;span&gt;&lt;span&gt; parents&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; document.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;getElementsByClassName&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;with-target-blank&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; parent&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; of&lt;&#x2F;span&gt;&lt;span&gt; parents) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        var&lt;&#x2F;span&gt;&lt;span&gt; links&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; parent.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;getElementsByTagName&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;a&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        for&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; link&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; of&lt;&#x2F;span&gt;&lt;span&gt; links) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;            if&lt;&#x2F;span&gt;&lt;span&gt; (link.hostname&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; !==&lt;&#x2F;span&gt;&lt;span&gt; window.location.hostname&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt; link.target&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ===&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                link.target&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;_blank&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;};&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;% if&lt;&#x2F;span&gt;&lt;span&gt; config.extra.enable_target_blank&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; %&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;enableTargetBlank&lt;&#x2F;span&gt;&lt;span&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;%&lt;&#x2F;span&gt;&lt;span&gt; endif&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; %&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This code snippet picks all the &lt;code&gt;&amp;lt;a&amp;gt;&lt;&#x2F;code&gt; elements on the page which are direct &#x2F;
indirect descendants of any elements containing the &lt;code&gt;with-target-blank&lt;&#x2F;code&gt; CSS
class. For all such links, we set the &lt;code&gt;target&lt;&#x2F;code&gt; attribute to &lt;code&gt;_blank&lt;&#x2F;code&gt;, in case
the links are external and if the &lt;code&gt;target&lt;&#x2F;code&gt; isn&#x27;t set to something already. And
all this code is defined in the base template so that it&#x27;s also available in all
the other templates (due to template inheritance).&lt;&#x2F;p&gt;
&lt;p&gt;As a result, you can now add the &lt;code&gt;with-target-blank&lt;&#x2F;code&gt; class on container elements
in which you want all the links to open in a new tab. On this site, for
instance, I do this only on elements containing Markdown rendered from Zola.
Here&#x27;s the relevant portion from the &lt;code&gt;page.html&lt;&#x2F;code&gt; template that I use.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; class&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;row&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; class&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;col with-target-blank&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        {{ page.content | safe }}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that for this to work, there are two pre-requisites.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;JavaScript needs to be enabled.&lt;&#x2F;li&gt;
&lt;li&gt;You should be able to modify the template code. I&#x27;m not sure if you can put
custom code in off-the-shelf themes that Zola provides. But since I
use a custom theme for this site, I didn&#x27;t have to look too much into this.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Other than that though, this approach is quite straight-forward and works
exactly like one would expect.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Building products for yourself</title>
        <published>2020-04-05T00:00:00+00:00</published>
        <updated>2020-04-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/building-products-for-yourself/"/>
        <id>https://sgoel.dev/posts/building-products-for-yourself/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/building-products-for-yourself/">&lt;p&gt;I&#x27;m a reasonable human being. I say that with a lot of confidence because I
don&#x27;t think I&#x27;ve ever wanted to build an Uber for squirrels (and I hope that in
the future, I may never want to).&lt;&#x2F;p&gt;
&lt;p&gt;I say this in the context of &quot;building things&quot;. You&#x27;re a bright software
developer. One day you notice that you have a personal itch that you want to
scratch. And then you realize that scratching the said itch could actually help
a lot of other people. So you decide to spend your evenings and weekends working
hard to build something.&lt;&#x2F;p&gt;
&lt;p&gt;We all know that feeling of pure excitement. You spend hours looking for a
project name (and ensure that the .com domain is available). You &lt;code&gt;mkdir -p&lt;&#x2F;code&gt; a
new directory, run &lt;code&gt;git init&lt;&#x2F;code&gt;, and take a screenshot of the root commit (just in
case your product ends up making you a millionaire). There&#x27;s nothing like it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;validating-ideas&quot;&gt;Validating ideas&lt;&#x2F;h2&gt;
&lt;p&gt;While all that is exciting, the only problem is that you don&#x27;t know if someone
else &lt;strong&gt;really&lt;&#x2F;strong&gt; would find your product useful. At this point, you only have an
idea, which by itself isn&#x27;t worth much unless you find someone else who would
like to use it and potentially even pay for it.&lt;&#x2F;p&gt;
&lt;p&gt;This is why the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.indiehackers.com&quot;&gt;indie software&lt;&#x2F;a&gt; circles stress on &quot;idea validation&quot; so much.&lt;&#x2F;p&gt;
&lt;p&gt;Before you commit to spending the next 6 months working on something, it makes
sense to check if there&#x27;s even a market. Besides, it&#x27;s possible to validate
ideas quite cheaply. You can search online for similar products or post on
online forums asking your potential customers if they would be interested in
your product. You can put up landing pages and collect emails. Some people go
even one step further and ask for pre-payments to make absolutely sure that this
is something for which people would be willing to pay.&lt;&#x2F;p&gt;
&lt;p&gt;While validating ideas is definitely good advice, I feel that too much of idea
validation can also sometimes get in the way.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;solving-your-own-problems&quot;&gt;Solving your own problems&lt;&#x2F;h2&gt;
&lt;p&gt;A different approach is to focus on solving your own problems.&lt;&#x2F;p&gt;
&lt;p&gt;Going back to the fact that I&#x27;m a reasonable human being, it&#x27;s very likely that
the problems I face are also legitimate. This in turn means that people who are
similar to me might be facing a similar problem, and hence might benefit from
the solution I build.&lt;&#x2F;p&gt;
&lt;p&gt;This approach makes things slightly easier. Instead of going through a formal
idea validation process, you can just bounce it off of a few friends of yours to
do some basic sanity checks. And if that goes well, you can proceed directly
towards the &quot;building&quot; stage.&lt;&#x2F;p&gt;
&lt;p&gt;What&#x27;s also nice about this approach is that since you&#x27;re building for yourself,
you&#x27;re essentially going to be a customer. This means that you&#x27;ll have access to
constant customer feedback to help you shape the final product.&lt;&#x2F;p&gt;
&lt;p&gt;Note that we didn&#x27;t completely get rid of idea validation. We&#x27;re still
validating ideas, but there&#x27;s no formal process. We&#x27;re not spending time setting
up landing pages or collecting emails (and if you&#x27;re in the EU, worrying about
the privacy aspects of collecting emails) or other such activities. We&#x27;re
essentially trusting our gut feeling to be right.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;This post is not an attempt to make blanket statements in favor of or against
idea validation. Most of the times you should definitely validate your ideas.
You don&#x27;t want to spend 6 months of your time and all your savings, working on a
product that has no market.&lt;&#x2F;p&gt;
&lt;p&gt;Instead, the message I want to convey is that setting up landing pages is
sometimes advertised as a rite of passage of sorts. Sure, you do want to make
sure that your product would have customers willing to pay for it. But at times,
it&#x27;s also OK to just trust your gut feeling, do a basic sanity-check on your
idea, treat yourself as the customer, and then immeditately get to work.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Moving from Pelican to Zola</title>
        <published>2020-02-11T00:00:00+00:00</published>
        <updated>2020-02-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/moving-from-pelican-to-zola/"/>
        <id>https://sgoel.dev/posts/moving-from-pelican-to-zola/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/moving-from-pelican-to-zola/">&lt;p&gt;Last weekend I moved this site over from &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;blog.getpelican.com&#x2F;&quot;&gt;Pelican&lt;&#x2F;a&gt; to &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.getzola.org&#x2F;&quot;&gt;Zola&lt;&#x2F;a&gt;. The motivations
were based partly in merit, but partly also just for fun.&lt;&#x2F;p&gt;
&lt;p&gt;The migration itself was mostly painless. The part that took the most amount of
time was just converting the posts from Pelican format to Zola format. Even
though both expect Markdown, I found that there were some minor differences here
and there (the front-matter syntax, for example). But nothing that a quick
Python script could not automate.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;pelican-zola&quot;&gt;Pelican &#x2F; Zola&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;ve been using Pelican for this site for about 4 years now. When I started out,
I chose Pelican because it was (and probably still is) the most popular static
site generator written in Python.&lt;&#x2F;p&gt;
&lt;p&gt;And I still think that Pelican is an excellent way of building static sites. You
get a lot of features out of the box. The documentation is excellent. And if you
need a particular feature that hasn&#x27;t been implemented yet, it&#x27;s easy to write a
plugin.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m not sure if there&#x27;s any specific disadvantage I can point to. Overall,
Pelican has been super nice to work with. I do know that people often complain
about themes, but I never had to muck around with installing third-party themes
so I can&#x27;t confirm how valid&#x2F;invalid those complaints are.&lt;&#x2F;p&gt;
&lt;p&gt;Looking back, I think I switched mostly because I&#x27;ve been trying to pick up
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.rust-lang.org&#x2F;&quot;&gt;Rust&lt;&#x2F;a&gt; in my free time. And I have this weird theory that the more I surround
myself with tools written in the language I&#x27;m trying to learn, the faster I&#x27;ll
be able to pick the said language up.&lt;&#x2F;p&gt;
&lt;p&gt;There may or may not be some truth to that statement. At least there&#x27;s a direct
analogy to learning languages that humans use to communicate. In the case of
machine languages though, the argumentation in my head is kind of arbitrary and
the effectiveness remains to be seen. Besides, this site is not some
&quot;real-world&quot; project generating revenue, so I don&#x27;t necessarily have to weigh
the trade-offs between development time and product impact. &quot;Playing around&quot;
is perfectly fine for a site like this.&lt;&#x2F;p&gt;
&lt;p&gt;So last Friday I went on to &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.staticgen.com&#x2F;&quot;&gt;StaticGen&lt;&#x2F;a&gt;, and picked up literally the first SSG
from the list that was written in Rust. This happened to be Zola.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;installing-zola&quot;&gt;Installing Zola&lt;&#x2F;h2&gt;
&lt;p&gt;The installation &quot;process&quot; was basically just downloading the latest release
from Github, and moving the included binary file over to &lt;code&gt;~&#x2F;.local&#x2F;bin&lt;&#x2F;code&gt;. Having
spent slightly more than a decade in the Python world where you have to do the
&lt;code&gt;virtualenv&lt;&#x2F;code&gt; dance every now and then, this was a nice surprise. To be clear, I
don&#x27;t think I&#x27;ve had huge problems with &lt;code&gt;virtualenv&lt;&#x2F;code&gt;. Combined with &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;python-poetry.org&#x2F;&quot;&gt;Poetry&lt;&#x2F;a&gt;, I
still feel it&#x27;s a reasonable way to manage Python applications. But it&#x27;s still
hard to beat static binaries.&lt;&#x2F;p&gt;
&lt;p&gt;I think the installation would&#x27;ve been even quicker if my distro would have had
Zola in the package repositories, but that would be a bit too optimistic of
Debian stable. 🤷‍♂️&lt;&#x2F;p&gt;
&lt;h2 id=&quot;speed&quot;&gt;Speed&lt;&#x2F;h2&gt;
&lt;p&gt;One of the next things I was pleasantly surprised about was &lt;strong&gt;speed&lt;&#x2F;strong&gt;. The fact
that Zola is written in Rust means that even without any particular
optimizations, it&#x27;s fast. Like, &lt;strong&gt;stupidly&lt;&#x2F;strong&gt; fast.&lt;&#x2F;p&gt;
&lt;p&gt;Incremental builds are almost always under 5ms, and building the entire site
from scratch takes ~100ms. That&#x27;s a huge difference from Pelican where even
incremental builds can easily take 2-3 seconds.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-migration&quot;&gt;The Migration&lt;&#x2F;h2&gt;
&lt;p&gt;Overall, the transition was fairly painless. The Zola documentation is pretty
clear on how to get started and organize things. So the majority of my time was
spent on converting the blog posts from Pelican format to Zola format (or
rather, writing a script that does that conversion).&lt;&#x2F;p&gt;
&lt;p&gt;While they both use Markdown, the front-matter syntax is different. Plus, tags
in Zola are defined as explicit taxonomies using a TOML-like syntax within the
front-matter, while in Pelican you can define tags directly like how you define
other fields.&lt;&#x2F;p&gt;
&lt;p&gt;I ended up writing a quick Python script to take the &lt;code&gt;content&#x2F;&lt;&#x2F;code&gt; directory from a
Pelican site and convert all your posts to a format that would be compatible
with Zola. This saved me a lot of time.&lt;&#x2F;p&gt;
&lt;script src=&quot;https:&#x2F;&#x2F;gist.github.com&#x2F;siddhantgoel&#x2F;e4647886832f06f4990425068c24fb84.js&quot;&gt;
&lt;&#x2F;script&gt;
&lt;p&gt;Please note that I wrote this script for my own personal setup. There&#x27;s a
reasonable chance that it may not work in your case, depending on if you&#x27;ve
arranged your content differently or there are slight differences here and there
in how your posts are formatted. But it might be worth a shot.&lt;&#x2F;p&gt;
&lt;p&gt;As for the HTML layouts, since I chose to write the theme myself, I had to look
up the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;tera.netlify.com&#x2F;&quot;&gt;Tera&lt;&#x2F;a&gt; templating syntax every now and then. Fortunately, it&#x27;s quite
similar to Jinja2, so it wasn&#x27;t that tricky.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;So far I&#x27;m happy with the migration. There are a ton of resources on the web
warning against rewrites. And most of them are true.&lt;&#x2F;p&gt;
&lt;p&gt;But I think that if the project under consideration is not business critical,
and you have some free time on your hands, and you want to play around with some
new technology, it&#x27;s OK to rewrite things. 🙃&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Frontend without writing CSS</title>
        <published>2020-01-08T00:00:00+00:00</published>
        <updated>2020-01-08T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/frontend-without-writing-css/"/>
        <id>https://sgoel.dev/posts/frontend-without-writing-css/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/frontend-without-writing-css/">&lt;p&gt;As a developer focusing primarily on writing backend software, I feel slightly
anxious when I have to work with CSS. In my head, the work involved in producing
correct and cross-browser compatible code feels monumental.&lt;&#x2F;p&gt;
&lt;p&gt;At the same time, I know how critical that skill is to product development. So,
to work around this anxiety, I&#x27;ve picked up a couple of approaches that let me
do frontend work without having to deal much with CSS (directly).&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve been able to test these out and implement them successfully on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;devtomanager.com&quot;&gt;Developer
to Manager&lt;&#x2F;a&gt;, so I feel fairly confident about sharing them publicly that they&#x27;ll
be of some use to others.&lt;&#x2F;p&gt;
&lt;p&gt;In this post, I&#x27;ll talk about some of these approaches that have worked for me.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;no-css&quot;&gt;No CSS&lt;&#x2F;h2&gt;
&lt;p&gt;Everyone knows that code is a liability. The more code you write, the more there
is to maintain, the more scope there is for bugs, so on and so forth. For any
given problem, I personally would prefer a solution that uses 10 lines of code
over one that uses 100 lines (no, code-golfing doesn&#x27;t count).&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve been trying to apply the same approach to frontend development.&lt;&#x2F;p&gt;
&lt;p&gt;To avoid writing CSS directly, I&#x27;ve been relying a lot on frameworks like
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;getbootstrap.com&#x2F;&quot;&gt;Bootstrap&lt;&#x2F;a&gt; and &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;bulma.io&#x2F;&quot;&gt;Bulma&lt;&#x2F;a&gt; because they provide a ton of primitives that I can use
as-is to implement web frontends.&lt;&#x2F;p&gt;
&lt;p&gt;These frameworks been around long enough, so in general I don&#x27;t feel the need to
worry about issues like stability or cross-browser compatibility. Unless you
still have to support Internet Explorer 6, you&#x27;ll be fine. And apart from
providing just helper classes, most of these frameworks also provide
ready-to-use components -- things like modals, cards, dropdowns, etc. -- as
well, which you can basically plug in to your project to get things working.&lt;&#x2F;p&gt;
&lt;p&gt;One caveat here is that this approach requires a lot of cooperation from the
designer you&#x27;re working with. If you&#x27;re set on using stock Bootstrap and are not
willing to write a single extra line of code, it&#x27;s important to convey this
limitation to the designer and see if they can work accordingly.&lt;&#x2F;p&gt;
&lt;p&gt;The chances of this working out are higher in a small team than in a large one.
Nevertheless, I feel that the advantages make it worth a try.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;simple-design&quot;&gt;Simple Design&lt;&#x2F;h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Perfection is achieved, not when there is nothing more to add, but when there
is nothing left to take away.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;I&#x27;ve found that simple designs are relatively easy to implement.&lt;&#x2F;p&gt;
&lt;p&gt;When I say &quot;simple&quot;, I mean designs where every single element on the page has
an extremely good reason to be there, and nothing feels unnecessary. Everything
on the page feels deliberate and serving a purpose.&lt;&#x2F;p&gt;
&lt;p&gt;Take &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zenhabits.net&#x2F;&quot;&gt;Zen Habits&lt;&#x2F;a&gt; for instance. All that you see on this page is a title, the
latest blog post, and a footer. There&#x27;s hardly anything &quot;extra&quot; on the page. And
even then, the intent of the page is crystal clear.&lt;&#x2F;p&gt;
&lt;p&gt;When designs are made following this line of thought, things are rather
straight-forward to implement. If you&#x27;re following the first approach and are
not too keen on writing CSS yourself, you can pick up any CSS framework of your
choice and use it without modifications to get your page ready. And in case you
don&#x27;t mind writing CSS, simple design reduces work so much that you can probably
get things done in half the time.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m not suggesting that every single page on the Internet should look like Zen
Habits. It would be boring if &lt;strong&gt;all&lt;&#x2F;strong&gt; websites are black and white.&lt;&#x2F;p&gt;
&lt;p&gt;The point instead is to think really carefully about what your page needs to
accomplish, what elements are absolutely crucial to getting it done, and go from
there.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;functional-css&quot;&gt;Functional CSS&lt;&#x2F;h2&gt;
&lt;p&gt;One more approach I&#x27;ve found to be helpful is using functional CSS frameworks
like &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;tailwindcss.com&#x2F;&quot;&gt;Tailwind&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;While conventional frameworks (like Bootstrap) provide predesigned UI
components, functional frameworks provide low-level utility CSS classes instead,
to help you build custom design without ever having to write a single line of
CSS.&lt;&#x2F;p&gt;
&lt;p&gt;As an example, to style a button so that it has a blue background and white
text, in the Bootstrap world you would probably add the &lt;code&gt;btn btn-info&lt;&#x2F;code&gt; classes
to the &lt;code&gt;&amp;lt;button&amp;gt;&lt;&#x2F;code&gt; element. In Tailwind, you&#x27;ll use something like &lt;code&gt;bg-blue-600 text-white&lt;&#x2F;code&gt; for a similar effect. &lt;code&gt;bg-blue-600&lt;&#x2F;code&gt; and &lt;code&gt;text-white&lt;&#x2F;code&gt; are utility
classes and are responsible for setting the background color of the target
element to a specific shade of blue (&lt;code&gt;bg-blue-600&lt;&#x2F;code&gt;), and the text color to white
(&lt;code&gt;text-white&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
&lt;p&gt;Having such lower level classes is pretty neat, because this lets you create
custom designs of your liking, without ever having to touch CSS code. All the
styling is encapsulated in these classes which all apply directly to the HTML
elements.&lt;&#x2F;p&gt;
&lt;p&gt;Personally, I haven&#x27;t played around much with frameworks like this. But I&#x27;ve
talked to friends who have had a great experience with Tailwind (and also
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;tachyons.io&#x2F;&quot;&gt;Tachyons&lt;&#x2F;a&gt;), so this is something I&#x27;d definitely recommend looking into.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;Before concluding this post, I&#x27;d like to add a big disclaimer here.&lt;&#x2F;p&gt;
&lt;p&gt;Most of all this is helpful &lt;strong&gt;only&lt;&#x2F;strong&gt; if the size of the team you&#x27;re working in
is small -- think 1 or 2 developers. Teams bigger than that would probably tend
to bring someone on who specializes in that skill anyway, so I&#x27;m not sure if all
this would even apply.&lt;&#x2F;p&gt;
&lt;p&gt;But in case this does apply to the setting you&#x27;re currently in, happy coding!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Gift a Donation</title>
        <published>2019-12-27T00:00:00+00:00</published>
        <updated>2019-12-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/gift-a-donation/"/>
        <id>https://sgoel.dev/posts/gift-a-donation/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/gift-a-donation/">&lt;p&gt;Are you trying to find a gift for your loved one(s) but can&#x27;t find anything and
it&#x27;s making you anxious instead?&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;d like to offer an escape hatch.&lt;&#x2F;p&gt;
&lt;p&gt;Open up your favorite search engine, look for an NGO that you identify with the
most, donate some money in the name of your loved one(s), print out the donation
certificate&#x2F;receipt, and gift that instead.&lt;&#x2F;p&gt;
&lt;p&gt;There are a couple of advantages of gifting donations.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;1. The gift would be definitely appreciated&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Often times when we don&#x27;t know for sure what the other person would like, we
tend to buy something that we &lt;strong&gt;think&lt;&#x2F;strong&gt; they&#x27;ll like.&lt;&#x2F;p&gt;
&lt;p&gt;Sometimes this works, but sometimes it also doesn&#x27;t and what you buy sooner or
later ends up in the trash. This way, not only is the gift not appreciated, but
you also end up wasting money which could have been put to better use.&lt;&#x2F;p&gt;
&lt;p&gt;So instead of buying something that the other person may or may not like, if you
donate some money in their name, they&#x27;ll most definitely appreciate the gesture.
And in the off chance that they don&#x27;t, sorry but IMHO it&#x27;s time to reconsider
that relationship.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;2. You did something good for others&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re reading this blog post, it&#x27;s highly likely that you&#x27;re privileged at
some level. Not only do you own the equipment to read this page (either a
computer or a mobile device), but you also have some spare time on your hand.&lt;&#x2F;p&gt;
&lt;p&gt;I feel that those of us who have the means and resources should give at least
some percentage of what we earn to those of us who don&#x27;t. Whatever percentage
that might be, is entirely your own personal choice.&lt;&#x2F;p&gt;
&lt;p&gt;I mention this because at the moment there is a lot of financial inequality in
the world. Most of the wealth is concentrated in the hands of a very few, and if
those of us towards the mid-low end of the spectrum would share some of what we
own with those of us at the lower end, the world would be a much better place.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;3. You helped fight consumerism&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The sheer number of advertisements that we&#x27;re exposed to on a daily basis is
mind-boggling, to the point where sometimes I&#x27;m even questioning the legality of
it all.&lt;&#x2F;p&gt;
&lt;p&gt;Every single company is trying to get you to buy something, independent of
whether you need it or not. So instead of some random marketing team playing
with your emotions to get you to buy something either for yourself or for a
loved one, use that money to improve someone else&#x27;s life instead.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;I&#x27;m not saying we should ban gifts altogether. Yes, we all have our wishlists
and it&#x27;s always nice to receive something from that list. And if, for instance,
you know that your partner would appreciate receiving that book they&#x27;ve
mentioned multiple times, by all means please go for it!&lt;&#x2F;p&gt;
&lt;p&gt;But if you don&#x27;t know what to gift, either ask the person, or make a donation in
their name. Let&#x27;s try to be a bit more deliberate with our choice of things to
buy and gift.&lt;&#x2F;p&gt;
&lt;p&gt;Happy Holidays!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Building things just because there is a market</title>
        <published>2019-11-04T00:00:00+00:00</published>
        <updated>2019-11-04T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/building-things-just-because-there-is-a-market/"/>
        <id>https://sgoel.dev/posts/building-things-just-because-there-is-a-market/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/building-things-just-because-there-is-a-market/">&lt;p&gt;Last week on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.vox.de&#x2F;cms&#x2F;sendungen&#x2F;die-hoehle-der-loewen.html&quot;&gt;Die Höhle der Löwen&lt;&#x2F;a&gt;, a startup called &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.drivedressy.com&#x2F;&quot;&gt;Drive Dressy&lt;&#x2F;a&gt; pitched
their product - custom designed seat covers for cars. The idea is to let you put
the metaphorical lipstick on those otherwise boring seat covers to give them a
more personalised and unique look.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;providing-value-and-making-money&quot;&gt;Providing value (and making money)&lt;&#x2F;h2&gt;
&lt;p&gt;In my (slightly old school) mind, a business means providing some sort of value
to someone in exchange for money. More often than not, this &quot;value&quot; means
solving a particular problem that they&#x27;re facing. This is the lens I use
whenever I want to form some thoughts about a given company. So far, how a
company looks like through this lens has correlated well with how good the said
company does in the market, so I find this model quite useful.&lt;&#x2F;p&gt;
&lt;p&gt;Interestingly though, this is not the only reason to start a business. In the
past decade or so, there have been a whole lot of startups coming up for a
variety of reasons, &quot;providing value for money&quot; not being one of them.&lt;&#x2F;p&gt;
&lt;p&gt;Startups with the sole business model of &quot;advertising&quot; fall under this category.
They don&#x27;t (usually) charge money from their users, hence building up a user
base of people who like having things for free. And since we all like having
things for free, eventually this user base is the &quot;value&quot; that the company
provides, which it sells in some way or the other to advertisers. So to be fair,
these companies certainly do exchange value for money, just not with their
users.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;there-s-a-market-for-that&quot;&gt;There&#x27;s a market for that&lt;&#x2F;h2&gt;
&lt;p&gt;Another reason why people start companies is that sometimes they see that
there&#x27;s a market, and irrespective of whether or not the market represents an
actual problem, decide to ride the train by building a product that serves
that specific market.&lt;&#x2F;p&gt;
&lt;p&gt;The good thing about this is that there&#x27;s a well-defined market, so the product
would obviously sell and the company is guaranteed to make at least some money.&lt;&#x2F;p&gt;
&lt;p&gt;The bad thing is that the product is still not solving any useful problem, and
might just be encouraging certain (for the lack of a better phrase) &quot;unnecessary
wants&quot; that people in that market have, or think they have. This is based on the
hypothesis that the said market exists because some people want it to exist.&lt;&#x2F;p&gt;
&lt;p&gt;And the worst thing about this is that building products for this market is just
taking away resources from our planet which could have been used for something a
lot more useful. Given the state of our planet right now, this is something that
bothers me &lt;strong&gt;a lot&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;drive-dressy&quot;&gt;Drive Dressy&lt;&#x2F;h2&gt;
&lt;p&gt;As far as I can tell, Drive Dressy seems to fit this description.&lt;&#x2F;p&gt;
&lt;p&gt;I, for one, certainly don&#x27;t see people around me complaining about how their
seat covers don&#x27;t represent their personalities. So it&#x27;s hard for me to say
whether this is an actual problem.&lt;&#x2F;p&gt;
&lt;p&gt;In their pitch, the founders mentioned that they have their target audience very
well defined (I can&#x27;t recall what it was, just that it exists), so there clearly
is a market for this. Good for them, because this means that the company would
make at least some amount of money.  But then again, is the product really
solving something useful? Or is it just encouraging people to spend money on
something that they don&#x27;t really need?&lt;&#x2F;p&gt;
&lt;p&gt;To be fair, I don&#x27;t want to demean Drive Dressy. The founders seem like
well-meaning people who clearly care about the work they do. While they were
unable to raise money on the show, there&#x27;s a company around the product, and the
founders have invested a lot of time and money into it and assembled a team, so
there&#x27;s at least &lt;strong&gt;some&lt;&#x2F;strong&gt; traction.&lt;&#x2F;p&gt;
&lt;p&gt;But after watching this episode (and in particular that pitch), I still can&#x27;t
help but think about the ethics of the whole situation.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;Is &quot;there&#x27;s a market for it&quot; a good enough reason to build a product (and
eventually a company around it)? Is that enough for us to obsess day and night
over a single idea, and invest a few hundred thousand euros to turn it into a
reality?&lt;&#x2F;p&gt;
&lt;p&gt;Different people would answer that question differently. This is not to draw a
clear line between good and bad (because that&#x27;s extremely difficult), but
instead to point to the fact that there are ethical grey zones in the startup
world which we should be aware of.&lt;&#x2F;p&gt;
&lt;p&gt;And given the romanticism that has developed around startups in the last 5-10
years, this is probably one of the most important questions that we should ask
ourselves before we consider starting a company.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>The switch from Arch to Debian</title>
        <published>2019-10-24T00:00:00+00:00</published>
        <updated>2019-10-24T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/the-switch-from-arch-to-debian/"/>
        <id>https://sgoel.dev/posts/the-switch-from-arch-to-debian/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/the-switch-from-arch-to-debian/">&lt;p&gt;About 3 weeks ago I switched my personal machine over to Debian Buster (also
stable at the moment).&lt;&#x2F;p&gt;
&lt;p&gt;For the past few years, I&#x27;ve almost exclusively been an
Arch Linux user. A few weeks back though, I felt that I would like to enable
full disk encryption, which required an OS re-install. Ideally I would&#x27;ve
installed Arch again, but this time around I simply wasn&#x27;t in the mood to go
through the installation process.&lt;&#x2F;p&gt;
&lt;p&gt;All I wanted was a system that picks minimal, safe, and boring defaults, so that
I can quickly move on to getting some actual work done.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;arch-debian&quot;&gt;Arch &amp;amp; Debian&lt;&#x2F;h2&gt;
&lt;p&gt;Before I start, I want to put it out there that this post is not to demean Arch.
Arch is an amazing distribution which I highly recommend to almost everyone.
It&#x27;s actually the one distro which put an end to my distro-hopping phase. And
the installation is part of what makes it Arch. Once you&#x27;re done setting it up,
you know exactly what components your system is composed of, and how everything
ties together and works together. And that makes you feel not just in complete
control, but it&#x27;s also kind of comforting, in the sense that in case things
break, you&#x27;re familiar enough with your system to know exactly where to look to
fix things.&lt;&#x2F;p&gt;
&lt;p&gt;But there&#x27;s also a downside. And that comes from the fact that my brain only has
a limited number of cycles to spare per day. Instead of keeping the state of my
machine in my mind, I would rather use those brain cycles to get some actual
work done. Yes, polishing your tools is important to increase your efficiency.
But I feel the ratio of polishing your tools to efficiency is similar to the
ratio of earning more money to happiness. After a certain point, the returns
start diminishing so the extra effort may not necessarily be worth it.&lt;&#x2F;p&gt;
&lt;p&gt;With this chain of thought, I decided to give Debian a try. And since
&quot;stability&quot; was a very important requirement for me, I decided to go with
&lt;code&gt;stable&lt;&#x2F;code&gt; instead of &lt;code&gt;testing&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;unstable&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The differences between the three are quite interesting though. Stable is
considered rock solid. Unstable is constantly changing, so any breakages are
almost immediately fixed. But the time it takes for those fixes to end up in
testing may vary, so if testing is broken, it can stay like that for a while.
So even though it&#x27;s slightly unintuitive, testing can be the most broken version
of Debian (for Debian&#x27;s definition of &quot;broken&quot;).&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, having decided to install stable, I clicked my way through the installer
and had a plain GNOME setup up and running in about an hour.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;first-impressions&quot;&gt;First impressions&lt;&#x2F;h2&gt;
&lt;p&gt;Prior to Debian, most of my experience with Linux has been with Arch, where
finishing the installation to get to a working desktop was a big deal. This time
around though, ending up on the desktop was pretty uneventful, which was a nice
change!&lt;&#x2F;p&gt;
&lt;p&gt;Things seemed to just work. I&#x27;m on a Thinkpad which is generally well supported
in Linux, so it was nice to see that things like bluetooth, keyboard hotkeys,
and sleep&#x2F;hibernate, were working out of the box. The only thing that didn&#x27;t
work out of the box was WiFi because it needed proprietary drivers, but fixing
that was as simple as adding the &lt;code&gt;non-free&lt;&#x2F;code&gt; repository and installing &lt;code&gt;iwlwifi&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;For some reason, Debian has a reputation of being a &quot;developer-only&quot;
distribution. Not sure what that even means, but so far I haven&#x27;t had to use my
&quot;developer-only&quot; skills.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;packages&quot;&gt;Packages&lt;&#x2F;h2&gt;
&lt;p&gt;Stable does have a reputation of having really old packages in its repositories.
And in the beginning it did bother me a little bit. But after using Debian for 3
weeks, it doesn&#x27;t seem like a huge deal-breaker.&lt;&#x2F;p&gt;
&lt;p&gt;The packages seem fairly recent to me (at least for now). And if you really want
the bleeding edge, you have the option to &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;backports.debian.org&#x2F;&quot;&gt;backport selected packages&lt;&#x2F;a&gt; to your
system to have their newer versions. So far I haven&#x27;t felt the need to do that,
but it&#x27;s good knowing that it&#x27;s possible.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;updates&quot;&gt;Updates&lt;&#x2F;h2&gt;
&lt;p&gt;Similar to the packages situation, I&#x27;ve noticed that &lt;code&gt;apt&lt;&#x2F;code&gt; doesn&#x27;t offer updates
too often. Probably because &lt;code&gt;stable&lt;&#x2F;code&gt; isn&#x27;t changing too much anyway (which is a
feature, not a bug). And to be honest, I feel a bit relieved when I see that not
a lot of packages need updating.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;siddhant@thinkpad&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; ~ % sudo apt list&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --upgradable&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Listing...&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; Done&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;siddhant@thinkpad&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; ~ %&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Previously, running software updates (especially kernel updates) was almost
always something that required active participation. My usual process was to
first check &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.archlinux.org&#x2F;news&#x2F;&quot;&gt;Arch Linux News&lt;&#x2F;a&gt; if there were any announcements, and then to check
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;archlinux&#x2F;&quot;&gt;reddit&lt;&#x2F;a&gt; if some others were reporting breaking updates. Only if everything
looked normal, would I go through with the update. It wasn&#x27;t normally a big
deal, and to be honest I&#x27;ve never had my Arch install break, but it wasn&#x27;t a
non-event either.&lt;&#x2F;p&gt;
&lt;p&gt;Now, I have the peace of mind to just update whenever I feel like. One, updates
aren&#x27;t that often anyway. And two, knowing how much importance stable puts on
being &lt;strong&gt;stable&lt;&#x2F;strong&gt;, updates are much less of an event.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;So far I&#x27;m satisfied with the switch. I feel that my system is now less
&quot;exciting&quot;, stays out of my way, and provides me with a solid base where I can
focus on getting work done. In the age where we&#x27;re constantly running after the
latest and the greatest, at times we tend to ignore the value that stability
provides (especially when the focus is on getting work done).&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>The Single Monitor Manifesto</title>
        <published>2019-10-16T00:00:00+00:00</published>
        <updated>2019-10-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/the-single-monitor-manifesto/"/>
        <id>https://sgoel.dev/posts/the-single-monitor-manifesto/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/the-single-monitor-manifesto/">&lt;p&gt;It&#x27;s common wisdom in the software engineering world that having more screens
translates to higher productivity. Tech companies don&#x27;t really shy away from
throwing money at getting additional monitors for their developers, under the
assumption that it&#x27;ll bring in productivity gains.&lt;&#x2F;p&gt;
&lt;p&gt;In this post, I&#x27;d like to question that assumption. My take on this is
anecdotal, but I know that I&#x27;m &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@housecor&#x2F;why-i-stopped-using-multiple-monitors-bfd87efa2e5b&quot;&gt;not the only one&lt;&#x2F;a&gt; to arrive at the same
conclusion.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;For the past few years, my setup has consisted of a 13&#x2F;14-inch laptop and one
additional screen. Given that most of my work in the last few years has focused
on web development, my rationale was that since I have the source code open on
the laptop, having an extra monitor would allow me to also display a terminal
and a browser at the same time, so that I spend less time switching back and
forth between windows.&lt;&#x2F;p&gt;
&lt;p&gt;After being on that setup for 7-8 years, this year I tried out the &quot;digital
nomad&quot; thing for about 3 months, which obviously meant I didn&#x27;t have my extra
monitor with me.&lt;&#x2F;p&gt;
&lt;p&gt;And what do you know.&lt;&#x2F;p&gt;
&lt;p&gt;I didn&#x27;t miss the extra screen &lt;strong&gt;at all&lt;&#x2F;strong&gt;. I discovered that I didn&#x27;t need to
have all those windows under focus at the same time, that all that
window-switching was not really necessary, and that many of the things which I
just assumed are requirements for me to get work done, are actually
nice-to-haves.&lt;&#x2F;p&gt;
&lt;p&gt;It has now been slightly more than a month since I&#x27;m back from all the
traveling, and I haven&#x27;t felt the need to use the extra monitor even once. Next
weekend I actually plan to put it up for sale on eBay.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Madness&lt;&#x2F;strong&gt;, right? I don&#x27;t think so. Here are a few reasons why this change
didn&#x27;t feel that drastic.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;high-resolution-screens&quot;&gt;High resolution screens&lt;&#x2F;h2&gt;
&lt;p&gt;Most laptops these days (that are worth buying) come with high resolution
screens. More pixels translates to more screen real-estate, which you can use by
either displaying more stuff within your window(s), or just cramming more
windows on the screen.&lt;&#x2F;p&gt;
&lt;p&gt;One of the main arguments for having a separate display is that you can move a
couple of windows off your main display to make it less cluttered. But if your
main display can show more windows, does that argument still apply?&lt;&#x2F;p&gt;
&lt;p&gt;To be clear, high resolution screens &lt;strong&gt;support&lt;&#x2F;strong&gt; the argument that you need more
screen space to get work done. YMMV, but I&#x27;ve discovered that screen space only
goes so far in increasing productivity. A minimum baseline is good. Anything
more than that wouldn&#x27;t necessarily make you more productive.&lt;&#x2F;p&gt;
&lt;p&gt;Personally, I&#x27;ve found that a 1920x1080 IPS display gives me enough space to see
what I&#x27;m currently trying to focus on. Having more than that wouldn&#x27;t
necessarily help me win the the web development olympics or something.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;focus&quot;&gt;Focus&lt;&#x2F;h2&gt;
&lt;p&gt;It has never been easier to get distracted. In some ways, I feel distraction is
running our economy right now. Every single company that has anything to do with
tech is competing for our attention via sending us real time updates, email
newsletters, push notifications, and what not. And when the distraction is not
external, we tend to distract ourselves by keeping Twitter or Email open on the
side. This is in complete contradiction to the fact that humans can only really
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.npr.org&#x2F;templates&#x2F;story&#x2F;story.php?storyId=95256794&quot;&gt;focus on one thing at a time&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Why do we do this? Why do we feel the need to display multiple things at the
same time, and when those things don&#x27;t fit on one screen, buy another one, when
all it&#x27;s going to do is make us distracted and take away from our ability to get
focused work done?&lt;&#x2F;p&gt;
&lt;p&gt;To me, one screen means one window has focus. This window is what I&#x27;m working on
right now, and what I intend to work on for as long as I can without getting
distracted. Personally, showing other windows on the side just decreases my
attention.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;clutter&quot;&gt;Clutter&lt;&#x2F;h2&gt;
&lt;p&gt;This one is a bit personal. I have a love&#x2F;hate relationship with cables. The
only cable I like is the ethernet cable because I get the warm fuzzies when my
laptop is connected via ethernet. All the other wires I hate. The extra monitor
I have adds &lt;strong&gt;two&lt;&#x2F;strong&gt; extra cables to the back of my desk. One for power and one
to connect to the laptop. &lt;strong&gt;Two&lt;&#x2F;strong&gt;. It&#x27;s nuts.&lt;&#x2F;p&gt;
&lt;p&gt;After doing the digital nomad thing, I&#x27;ve been making a conscious effort to
reduce the clutter in my life as much as I can. This requires constant
questioning at every stage. Every &quot;thing&quot; that I interact with, I ask myself, do
I &lt;strong&gt;need&lt;&#x2F;strong&gt; this? Is this absolutely critical to my everyday life? And if I
didn&#x27;t have it, would I miss it?&lt;&#x2F;p&gt;
&lt;p&gt;For the second monitor (and all of its cables), I just couldn&#x27;t find a good
answer to that question.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;Productivity is something highly personal and contextual. What works for me may
not work for you. And what works for a software developer may not work for a
designer (or the other way around). But I do believe that it sometimes helps to
question the status-quo, and ask ourselves do we really need the things we use?&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re a software developer working on similar things as I am, I would
strongly encourage you to put those extra monitor(s) away for a week and see if
you notice an increase&#x2F;decrease in productivity. Chances are, it wouldn&#x27;t
matter.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Mountains</title>
        <published>2019-07-30T00:00:00+00:00</published>
        <updated>2019-07-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/mountains/"/>
        <id>https://sgoel.dev/posts/mountains/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/mountains/">&lt;p&gt;People often ask me whether I enjoy hiking. Living in Munich, it&#x27;s no
coincidence that the mountains are a huge part of life for the people who live
nearby. So every now and then, the conversations I have tend to take a turn in
that direction.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s a tricky question to answer. &lt;strong&gt;Do&lt;&#x2F;strong&gt; I enjoy hiking?&lt;&#x2F;p&gt;
&lt;p&gt;Well, yes, and no. I have a complicated relationship with mountains.&lt;&#x2F;p&gt;
&lt;p&gt;I was born in New Delhi, which, for those who aren&#x27;t familiar, is this giant
cosmopolitan which seems to keep growing. But the one thing that is not within
its boundaries, is mountains. The Indian sub-continent is home to most of the
highest peaks in the world, but all of them (not that I would go hiking there
for fun anyway) are at least a few hundred kilometers away.&lt;&#x2F;p&gt;
&lt;p&gt;The point is, mountains weren&#x27;t a part of my growing up.&lt;&#x2F;p&gt;
&lt;p&gt;But a few things have changed since then. I&#x27;ve started calling Munich as my
home, and have married someone who spent literally all her childhood exploring
the spectacular mountain range of the Alps. This, and the close proximity that
Munich has to the Alps, is probably why I&#x27;ve started finding myself more and
more in the mountains.&lt;&#x2F;p&gt;
&lt;p&gt;I wouldn&#x27;t call the transition linear. In this article, I&#x27;ll try my best to
describe how it all happened.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;first-steps&quot;&gt;First steps&lt;&#x2F;h2&gt;
&lt;p&gt;I can&#x27;t recall exactly how I felt when I first stood before a mountain (with an
intention to climb it).&lt;&#x2F;p&gt;
&lt;p&gt;The (intentionally) hectic life of big cities and the calm atmosphere that
mountains offer do not exactly go hand-in-hand. I&#x27;ve had the chance to
experience this first hand. Being used to the &quot;big-city&quot; feeling and only
knowing nature from the books, it was quite interesting to &lt;strong&gt;experience&lt;&#x2F;strong&gt; nature
at its absolute raw. So I&#x27;ve had my share of conflicts, both internal and with
other more enthusiastic hikers (aka the Mrs).&lt;&#x2F;p&gt;
&lt;p&gt;But the conflicts have since then reduced. Hiking is something I&#x27;ve started to
enjoy a little bit. And while I can&#x27;t recall exactly how I felt when I first
stood before a mountain, wanting to climb it, it&#x27;s safe to say that it was very
similar to how I feel now when I go for a hike.&lt;&#x2F;p&gt;
&lt;p&gt;I see this giant piece of rock, right there in front of me. Calm, serene, a bit
arrogant, but most of all, majestic. I know that this thing was formed because
the planet decided to make some random movements in random directions, but it
still feels designed. As if someone literally chiselled away at it until it was
perfect.&lt;&#x2F;p&gt;
&lt;p&gt;I want to climb it, I think? The Mrs is usually more enthusiastic about this
than I am, so I guess a better way to describe it is that I want to want to
climb it. But the mountain thinks otherwise. It just stands there, completely
ignoring of all that is going on in my head. Kind of like saying - you know, I
see your point, but I don&#x27;t really buy it. I doubt myself whether I would be
able to make it all the way to the top. I&#x27;m a bit unsure, but there&#x27;s only one
way to find out. So I start walking.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-climb&quot;&gt;The climb&lt;&#x2F;h2&gt;
&lt;p&gt;The beginnings are all well and good. This is not so bad, I usually think to
myself. Maybe it just looks harder than it actually is.&lt;&#x2F;p&gt;
&lt;p&gt;Slowly though, as the ascent steepens, my mind starts playing a version of
good-cop-bad-cop. With every single step I take, the bad-cop asks me to make a
turn back and go home instead. How much better would it be if I was on the couch
right now. But then the good-cop kicks in with its own arguments. Don&#x27;t I want
to burn all the calories I gained from that pizza yesterday? Wouldn&#x27;t it be nice
to have some physical movement for a change?&lt;&#x2F;p&gt;
&lt;p&gt;This whole spiel continues basically until I reach the top. It is in these
moments that I realize how fascinating my mind&#x27;s ability to make up BS really
is.&lt;&#x2F;p&gt;
&lt;p&gt;Another cool thing about the climb is how quickly the landscape changes. As you
gain height, things on the ground start appearing smaller and smaller. You start
seeing more and more peaks around you. The nature gets more and more pristine.
And if you started hiking early in the morning, you have it basically all to
yourself. It&#x27;s also a good time for you and your SO to have conversations beyond
the daily drill, in case you happen to be hiking with your SO.&lt;&#x2F;p&gt;
&lt;p&gt;You see people of all kinds walking past. The single-backpackers, the ones who
run (!) up the mountain, the city-couple trying to walk up in fancy shoes&#x2F;heels,
the slightly older crowed (which btw is a group that makes the bad-cop feel
especially bad). Normally while on the ground, I probably wouldn&#x27;t strike up a
conversation with them at random. On the climb however, given that we share the
common goal of reaching the top, it feels a bit more natural to smile and say
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.thelocal.de&#x2F;20190313&#x2F;servus&quot;&gt;Servus&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-peak&quot;&gt;The peak&lt;&#x2F;h2&gt;
&lt;p&gt;Reaching the peak is always a special moment. We usually start quite early
(often times starting the climb at 5.30 in the morning), so we also tend to
reach the top really early. So a nice consequence is that we have the entire
peak to ourselves until a few others join in.&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, I again can&#x27;t recall exactly how I felt when I first reached the top of
a mountain. But it was pretty similar to how I feel now.&lt;&#x2F;p&gt;
&lt;p&gt;I feel small. Everything that I see around me that&#x27;s not a part of the mountain
I&#x27;m on, is at least a few kilometers away. It looks much smaller than it
actually is, because, well, physics. My mind somehow flips the whole thing over.
It makes me feel tiny. As if in the grand scheme of things, I don&#x27;t matter. Not
a single one of us matters. All the stuff that we argue over in our daily lives -
the inconveniences, things not going according to what we planned, traffic
jams, trains getting delayed, appointments, fights - everything just vanishes. I
see the ground from the peak, and everything looks tiny, insignificant,
inconsequential, kind of like a rounding error in the larger scheme of things.&lt;&#x2F;p&gt;
&lt;p&gt;At the same time, I feel at one with nature. That&#x27;s the one time when I feel
&lt;strong&gt;connected&lt;&#x2F;strong&gt; to this planet that we call home. I look up and I see the sky. I
look down and I see the ground. Raw, unprocessed, untouched, ground. That&#x27;s
basically all there is to it. And yet, it gives me this weirdly spiritual
experience that I find hard to put down in words.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;So &lt;strong&gt;do&lt;&#x2F;strong&gt; I enjoy hiking? Well, yes, and no. But more yes than no.&lt;&#x2F;p&gt;
&lt;p&gt;I don&#x27;t particularly enjoy taking the first steps to climb up a mountain. I hate
pushing my physical boundaries, straining my legs, the constant
being-out-of-breath while you&#x27;re tired, constant back and forth between the two
sides of my head arguing with each other if going hiking was a good decision.&lt;&#x2F;p&gt;
&lt;p&gt;But once I reach the top, I realize how pointless all that back and forth was,
and yet still, how I would probably do all of it over again to experience how it
is like to be at the top.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Offsite backups using Restic</title>
        <published>2019-07-12T00:00:00+00:00</published>
        <updated>2019-07-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/offsite-backups-using-restic/"/>
        <id>https://sgoel.dev/posts/offsite-backups-using-restic/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/offsite-backups-using-restic/">&lt;p&gt;I&#x27;m probably not the first person to say this, but backups are important. A
simple search on the web for &quot;importance of backups&quot; can explain this point
much better than I can. Hard drives can be moody, so it just makes sense to
prepare for the case when they&#x27;re in a bad mood.&lt;&#x2F;p&gt;
&lt;p&gt;As a responsible citizen, I&#x27;ve been making weekly backups of my data for a few
years now. All this while I&#x27;ve kept things rather simple. I&#x27;ve had a 1TB HD
connected to a spare Raspberry Pi at home connected to the local network. Every
now and then I would just run a simple shell script that would &lt;code&gt;rsync&lt;&#x2F;code&gt; my entire
&lt;code&gt;~&lt;&#x2F;code&gt; directory over to this HD.  This was simple, quick to setup, and worked
quite well in practice.&lt;&#x2F;p&gt;
&lt;p&gt;Every now and then though, I&#x27;ve had troubles mounting&#x2F;unmounting this disk to
other machines. I can&#x27;t exactly put a finger on what it was. Maybe it&#x27;s my
tendency to misinterpret the &quot;you can now safely eject the USB disk&quot; message
from the operating system. Or maybe the fact that between my wife and I, we use
Windows, Mac, &lt;strong&gt;and&lt;&#x2F;strong&gt; Linux, so this disk has had to do a fair bit of context
switching. Or, who knows, maybe the disk was just plain moody.&lt;&#x2F;p&gt;
&lt;p&gt;This always made me feel slightly uneasy. There&#x27;s no point of having backups
when your backup destination is unstable, right? So a few weeks ago, I started
looking for options.&lt;&#x2F;p&gt;
&lt;p&gt;Turns out that the only other alternative is storing your data on &lt;s&gt;the
cloud&lt;&#x2F;s&gt; someone else&#x27;s computer. This does have the potential to make one feel
a little uneasy. But if you throw encryption into the equation, it&#x27;s actually
not all that bad. It turns the whole thing into a question of probability. Yes,
your data lives on &lt;s&gt;the cloud&lt;&#x2F;s&gt; someone else&#x27;s computer, but given the
computing resources we have &lt;strong&gt;today&lt;&#x2F;strong&gt;, and given good enough encryption, the
probability that a bad actor can look through your data is quite low.&lt;&#x2F;p&gt;
&lt;p&gt;Which then means that finding a good offsite backup solution boils down to
finding a piece of software whose encryption implementation you can trust. While
I do understand the high&#x2F;medium-level of what encryption involves, I&#x27;m not an
expert on the specifics. But if someone who does understand the lower levels
can recommend a particular piece of software, that&#x27;s good enough for me.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s how I found &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;restic.net&quot;&gt;Restic&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Restic is a fast and secure backup program. Its design goals are explicitly
stated on its website, but the ones that appealed to me the most were
ease-of-use and secure cryptography.&lt;&#x2F;p&gt;
&lt;p&gt;Here are a few more reasons why I decided to use it to make backups of my own
data.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Restic &lt;strong&gt;assumes&lt;&#x2F;strong&gt; that the storage target is an untrusted environment. When
you have something like that as an &lt;strong&gt;assumption&lt;&#x2F;strong&gt;, I don&#x27;t think you can do
much wrong there.&lt;&#x2F;li&gt;
&lt;li&gt;The &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;restic&#x2F;restic&#x2F;blob&#x2F;master&#x2F;doc&#x2F;design.rst&quot;&gt;design&lt;&#x2F;a&gt; is open, and all the source code is freely available on
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;restic&#x2F;restic&quot;&gt;Github&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;The &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;restic.readthedocs.io&#x2F;en&#x2F;stable&#x2F;070_encryption.html&quot;&gt;encryption&lt;&#x2F;a&gt; that Restic uses is well documented, and also reviewed by
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;blog.filippo.io&#x2F;restic-cryptography&#x2F;&quot;&gt;others&lt;&#x2F;a&gt; who understand encryption much better than I do.&lt;&#x2F;li&gt;
&lt;li&gt;Most storage providers are natively supported, which is super nice and makes
it really easy to get started.&lt;&#x2F;li&gt;
&lt;li&gt;It&#x27;s extremely &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;restic.readthedocs.io&#x2F;en&#x2F;latest&#x2F;&quot;&gt;well documented&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;I have a personal AWS account, so I decided to set up an S3 bucket in the
Frankfurt region to use as the storage backend. I also had to generate AWS
access credentials that Restic needed to read&#x2F;write to the S3 bucket. But
obviously you only have to do this if you decide to use S3. Different storage
backends have different preliminary steps you need to perform.&lt;&#x2F;p&gt;
&lt;p&gt;After setting that tiny bit of infrastructure up, the process of setting up
Restic itself was relatively straight-forward. I mostly just followed the
documentation step by step and could get everything to work in hardly any time,
and without any hiccups. Having worked with software for close to a decade now,
I love it when this happens!&lt;&#x2F;p&gt;
&lt;p&gt;I first used &lt;code&gt;restic init&lt;&#x2F;code&gt; to initialize the storage backend (S3 in my
case), after which Restic was ready to start adding data. I then used the
&lt;code&gt;restic backup&lt;&#x2F;code&gt; command to add data to the backups. That was basically it.&lt;&#x2F;p&gt;
&lt;p&gt;Every now and then it&#x27;s a good idea to run &lt;code&gt;restic prune&lt;&#x2F;code&gt; so it can clean things
up internally, and&#x2F;or remove older backups which are not needed anymore, but for
regular use, &lt;code&gt;restic backup&lt;&#x2F;code&gt; is probably the only command you&#x27;ll end up using.&lt;&#x2F;p&gt;
&lt;p&gt;And since backups are pointless if you can&#x27;t easily restore them, the &lt;code&gt;restic restore&lt;&#x2F;code&gt; command restores your encrypted backups to your local disk to a
location of your choice.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;So far I&#x27;m pretty happy with this setup. Restic&#x27;s design goals are exactly what I
would look for in a backup program, and the design choices it has made along the
way makes the backup-process not just &lt;strong&gt;easy&lt;&#x2F;strong&gt;, but borderline &lt;strong&gt;fun&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re looking for a backup solution, this is highly recommended!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>How do you decide what to learn next?</title>
        <published>2019-06-19T00:00:00+00:00</published>
        <updated>2019-06-19T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/how-do-you-decide-what-to-learn-next/"/>
        <id>https://sgoel.dev/posts/how-do-you-decide-what-to-learn-next/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/how-do-you-decide-what-to-learn-next/">&lt;p&gt;Disclaimer: this is not professional career advice and should not be treated as
such.&lt;&#x2F;p&gt;
&lt;p&gt;One question I&#x27;ve been thinking a lot about these days is “how do you decide
what to learn next”? There are multiple aspects to this, and to be honest my
head is all over the place on this one. This post will basically be a brain
dump, and may or may not lead to a conclusive result.&lt;&#x2F;p&gt;
&lt;p&gt;To start with, I feel we can divide this question into two more specific ones -&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;How do you decide what to learn &lt;strong&gt;as a beginner&lt;&#x2F;strong&gt;?&lt;&#x2F;li&gt;
&lt;li&gt;How do you decide what to learn &lt;strong&gt;next&lt;&#x2F;strong&gt;?&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;I&#x27;ve talked to a lot of people who have been starting out with programming, and
one question I get asked a lot is - “what should I learn”. Not &lt;strong&gt;everyone&lt;&#x2F;strong&gt; asks
this, but I&#x27;ve had enough people ask me this question for me to start thinking
it might just be a common theme.&lt;&#x2F;p&gt;
&lt;p&gt;On the other hand, people who have already been programming for a while may at
some point feel the need to do something else. We are all wired differently, but
common reasons include (but are not limited to) -&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;getting too comfortable with the current set of technologies at $WORK&lt;&#x2F;li&gt;
&lt;li&gt;struggling to separate the hype from what&#x27;s real (speaking from personal
experiences, this is easy to dismiss but can be quite a struggle)&lt;&#x2F;li&gt;
&lt;li&gt;the need to try something “new”&lt;&#x2F;li&gt;
&lt;li&gt;fear of missing out or being left behind&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Without knowing a definitive answer or claiming that I know of a bullet-proof
approach to be able to answer questions along similar lines, I feel one can
apply a few different approaches here to reach a slightly less confused state.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-do-i-want-to-optimize-for&quot;&gt;What do I want to optimize for?&lt;&#x2F;h2&gt;
&lt;p&gt;One approach is to take a step back completely, and try to ask instead the
question “what do I want to optimize for”? A common piece of advice in the
software industry is to &quot;optimize for learning&quot;. Vague as it may be, it&#x27;s an
interesting approach nevertheless.&lt;&#x2F;p&gt;
&lt;p&gt;Once you ask this question, your mind starts giving you all these &quot;meta&quot;
responses. It might tell you that you want to optimize for learning how to build
low latency web services, or data science, or functional programming, or
whatever. You can then take the answer to &lt;strong&gt;this&lt;&#x2F;strong&gt; question, which should give
you a few pointers on how to answer your original question.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-skillset-is-kind-of-sort-of-adjacent-to-my-current-one&quot;&gt;What skillset is kind-of sort-of adjacent to my current one?&lt;&#x2F;h2&gt;
&lt;p&gt;Another relatively common piece of advice is to learn something that&#x27;s
&lt;em&gt;different enough&lt;&#x2F;em&gt; from your current skillset. The idea is that if you learn
something that&#x27;s too similar, you won&#x27;t really be learning anything. But if you
pick up something completely orthogonal, you would probably just get discouraged
quickly, foiling the entire plan.&lt;&#x2F;p&gt;
&lt;p&gt;This also seems to confirm the theory that our brains feel the most productive
when they&#x27;re working &lt;em&gt;slightly&lt;&#x2F;em&gt; outside their comfort zones, but not too much. I
can&#x27;t find the source right now, but spending some time on a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;duckduckgo.com&quot;&gt;good search
engine&lt;&#x2F;a&gt; should help.&lt;&#x2F;p&gt;
&lt;p&gt;One example would be that if you&#x27;re a backend developer, you could look into
improving your CSS skills. Not sure if this is a good example, but you get the
idea.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-skillset-would-augment-my-current-one&quot;&gt;What skillset would augment my current one?&lt;&#x2F;h2&gt;
&lt;p&gt;We don&#x27;t have to always think in terms of writing code. There are plenty of
non-programming skills that can help you become a better software developer.&lt;&#x2F;p&gt;
&lt;p&gt;For example, being able to write well is something that goes a long way in
helping you communicate your thoughts to others. Software development is a team
activity, so if you&#x27;re able to write well, you&#x27;ll be able to (as a team) develop
better software.&lt;&#x2F;p&gt;
&lt;p&gt;Another example is having good communication skills. A lot of my non-tech
friends laugh about how difficult it is to communicate with developers, and how
only a special kind of person can do something like that. We laugh it out and it
makes for a good conversation and everything, but at the back of my mind I&#x27;m
thinking “you know what, this is actually not funny”. Think about how much it&#x27;ll
improve your career if you could translate developer-speak to
other-people-at-the-company-speak. Not only is this one of the core attributes
of being a good engineering manager (if that&#x27;s a direction you&#x27;d like to take),
improving your communication skills in general helps you become a well-rounded
person.&lt;&#x2F;p&gt;
&lt;p&gt;Personal anecdote: I&#x27;ve been the person who stands in the corner nursing his
beer at a party, and I&#x27;ve also been the person who makes an actual effort at
communicating with other people and succeeds, and every single day I prefer
being the second version.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;programming-is-just-a-means-to-an-end-anyway&quot;&gt;Programming is just a means to an end anyway.&lt;&#x2F;h2&gt;
&lt;p&gt;One school of thought which many subscribe to is to consider programming as a
tool supposed to solve problems. Code is not useful by itself, unless it&#x27;s
solving somebody else&#x27;s problem.&lt;&#x2F;p&gt;
&lt;p&gt;So all the beauty and elegance that one might optimize for when writing software
doesn&#x27;t really matter, because your users won&#x27;t see&#x2F;understand it anyway.  Your
job is just to take what your users want, and figure out a way to solve their
problems using the minimal set of resources in the fastest possible time
producing the most reliable result.&lt;&#x2F;p&gt;
&lt;p&gt;When I think of producing something reliable quickly using the least possible
set of resources, I have the feeling this is kind of like saying “use the right
tool for the job”&lt;sup&gt;TM&lt;&#x2F;sup&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;So depending on who your users are and what their problem is, it should be
possible to find the technology you should be learning as opposed to certain
other ones, which is kind-of the answer we were looking for?&lt;&#x2F;p&gt;
&lt;p&gt;Or in other words, find a problem to solve, which then tells you what technology
to pick, and then go ahead and learn it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;would-it-increase-my-chances-of-finding-a-job&quot;&gt;Would it increase my chances of finding a job?&lt;&#x2F;h2&gt;
&lt;p&gt;We all want to find a way to earn money with our skills so we can provide for
ourselves and our loved ones. So optimizing for being employable is not
necessarily wrong. The problem, in my experience, comes from decision paralysis.&lt;&#x2F;p&gt;
&lt;p&gt;There are a ton of software companies building different kinds of software. And
the ones who have a strong online presence swear by the technologies that
they&#x27;re using. So when we&#x27;re trying to optimize for employability, we basically
have to sift through all this noise and identify the technologies that would
increase our chances of getting a job in the industry.  Besides, some
languages&#x2F;frameworks are pushed more than the others, which only adds to the
confusion.&lt;&#x2F;p&gt;
&lt;p&gt;Another problem here is that more often than not, the market is driven by hype.
Millions of VC dollars being poured into companies who are riding on the hype
train is enough to make anyone confused as to what&#x27;s real and what&#x27;s not. Unless
you have good experience&#x2F;intuition on how to distinguish hype from substance,
this decision can require a &lt;strong&gt;lot&lt;&#x2F;strong&gt; of willpower and can be extremely tricky.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-feels-fun-to-me&quot;&gt;What feels fun to me?&lt;&#x2F;h2&gt;
&lt;p&gt;A lot of us program because it&#x27;s just &lt;strong&gt;fun&lt;&#x2F;strong&gt;. We started back in high school,
wrote small scripts here and there, picked up new things which we found
interesting, just for the heck of it. We code because we &lt;strong&gt;like&lt;&#x2F;strong&gt; writing code.&lt;&#x2F;p&gt;
&lt;p&gt;The approach here would be to activate kid-mode, and make a decision without
giving any thought to whether or not it&#x27;s the optimal one.  Sometimes it&#x27;s also
OK to decide without having any ultimate goals in mind.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re capable of ignoring the grown-up-voice in your head that&#x27;s questioning
your decision and is instead asking you to make one that&#x27;s more “informed”, you
can get to an answer pretty quickly following this approach.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-are-my-values&quot;&gt;What are my values?&lt;&#x2F;h2&gt;
&lt;p&gt;This is something slightly more involved. If you can figure out your core values&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;the attributes that you strive towards achieving in your life and appreciate
in the things around you - it&#x27;s possible to find areas in technology that let
you be closer to those values.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;For instance, if you really value privacy, there are plenty of developments
happening on the web that can provide you with exciting opportunities on what to
work on next (and ergo, what to learn next). Or if you really value correctness,
formal methods might be something you could consider.&lt;&#x2F;p&gt;
&lt;p&gt;This can require a little bit of soul-searching. I recommend sitting down in a
quiet room with a pen and paper, &lt;strong&gt;without&lt;&#x2F;strong&gt; any electronic devices, and just
start writing your thoughts down. It may take a while before you get some
clarity, but it&#x27;ll be worth it.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;I&#x27;m pretty sure there are more mental models that one can use to answer
questions along similar lines. As mentioned right at the beginning, this blog
post does not provide a conclusive answer, but I feel that the approaches listed
out here should provide a good starting point to get to where you want to be.&lt;&#x2F;p&gt;
&lt;p&gt;The most important thing out of all this, though, is that irrespective of the
decision you make or the approach you use, it&#x27;s important to &lt;strong&gt;stick to your
decision&lt;&#x2F;strong&gt;. We often have the tendency to leave things halfway because of
reasons&lt;sup&gt;TM&lt;&#x2F;sup&gt;. Well, at least I do. This is plain counter-productive.
I&#x27;ve learned this the hard way, and would like to help as many people as
possible avoid falling into that trap.&lt;&#x2F;p&gt;
&lt;p&gt;Good luck!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Not everything needs to be async</title>
        <published>2019-05-25T00:00:00+00:00</published>
        <updated>2019-05-25T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/not-everything-needs-to-be-async/"/>
        <id>https://sgoel.dev/posts/not-everything-needs-to-be-async/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/not-everything-needs-to-be-async/">&lt;p&gt;Writing asynchronous code is popular these days. Look at this search trend from
the last 5 years.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;sgoel.dev&#x2F;posts&#x2F;not-everything-needs-to-be-async&#x2F;async-python.png&quot; alt=&quot;Async Python&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I have the feeling that the number of tutorials on the internet explaining
asynchronous code has increased quite a bit since Python started supporting the
&lt;code&gt;async&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;await&lt;&#x2F;code&gt; keywords. Even though Python has always had support for running
asynchronous code using the &lt;code&gt;asyncore&lt;&#x2F;code&gt; module (or using libraries like
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;twistedmatrix.com&#x2F;&quot;&gt;Twisted&lt;&#x2F;a&gt;), I don&#x27;t think that &lt;code&gt;asyncore&lt;&#x2F;code&gt; was used as much as the new &lt;code&gt;asyncio&lt;&#x2F;code&gt;.
This is a pure gut feeling though; I have no numbers to back that claim up.&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, &lt;code&gt;asyncio&lt;&#x2F;code&gt; makes it slightly easier to write asynchronous code. Slightly,
because I don&#x27;t know if I can call the API as intuitive, or dare I say,
&quot;Pythonic&quot;. &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;lucumr.pocoo.org&#x2F;2016&#x2F;10&#x2F;30&#x2F;i-dont-understand-asyncio&#x2F;&quot;&gt;This article&lt;&#x2F;a&gt; does a much better job of explaining why &lt;code&gt;asyncio&lt;&#x2F;code&gt; is
what it is.&lt;&#x2F;p&gt;
&lt;p&gt;Even if we put &lt;code&gt;asyncio&lt;&#x2F;code&gt; aside, I don&#x27;t think asynchronous code is &lt;strong&gt;ever&lt;&#x2F;strong&gt;
easy. There&#x27;s just so much going on under the hood that it&#x27;s difficult to keep
your head from spinning, before you can actually get to writing the application
logic.&lt;&#x2F;p&gt;
&lt;p&gt;But that&#x27;s not what this blog post is about. This blog post is about how not
everything &lt;strong&gt;needs&lt;&#x2F;strong&gt; to be async. And that if some code you&#x27;re working on
absolutely necessarily must be async, then why it makes sense to stop for a
minute and consider the consequences of introducing this extra level of
complexity.&lt;&#x2F;p&gt;
&lt;p&gt;This has nothing to do with Python, or &lt;code&gt;asyncio&lt;&#x2F;code&gt;, or any async framework in
general. All I want to say, is, if you think you want to write asynchronous
code, think twice.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;synchronous-is-much-simpler&quot;&gt;Synchronous is much simpler&lt;&#x2F;h2&gt;
&lt;p&gt;Synchronous code is simple to write. It&#x27;s also much easier to reason about, and
it&#x27;s lot less likely to contain concurrency or thread-safety bugs than
asynchronous code. As programmers, our job is to solve business problems
reliably in the least possible time. Synchronous code fits that criteria quite
well. So if I&#x27;m given a choice between writing synchronous or asynchronous, I
can say with a reasonable amount of confidence that I&#x27;ll prefer synchronous.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;would-async-really-help&quot;&gt;Would async really help?&lt;&#x2F;h2&gt;
&lt;p&gt;Next, if asynchronous code is absolutely required, it makes sense to think about
what it&#x27;s going to do underneath, and what performance gains it&#x27;s going to
bring.&lt;&#x2F;p&gt;
&lt;p&gt;For instance, if you&#x27;re writing a web request handler which calls out a few
external APIs and combines those responses to finally return a response to your
user, yes, asynchronous code would absolutely help. The time that the external
resources make your request handler wait can be used to serve other user
requests.&lt;&#x2F;p&gt;
&lt;p&gt;On the other hand, if your request handler is fetching a few rows from a
database server that&#x27;s running on the same machine as the app server, it&#x27;s not
going to make &lt;strong&gt;that&lt;&#x2F;strong&gt; much of a difference if it were async.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;is-it-safe&quot;&gt;Is it safe?&lt;&#x2F;h2&gt;
&lt;p&gt;Often times we end up using abstractions that hide away the implementation
details and provide a nice API for us to work with. In these cases, it&#x27;s
important to know what exactly is being hidden, or how that abstraction is
working underneath.&lt;&#x2F;p&gt;
&lt;p&gt;For example, Python provides an abstraction called &lt;code&gt;ThreadPoolExecutor&lt;&#x2F;code&gt;, which
allows you to run functions in separate threads (there is also
&lt;code&gt;ProcessPoolExecutor&lt;&#x2F;code&gt; which lets you separate things on a process-level).&lt;&#x2F;p&gt;
&lt;p&gt;The way this works is that you submit a callable to the pool, and the pool
returns a &lt;code&gt;Future&lt;&#x2F;code&gt; object immediately. And when the function has finished
running, the results (or the exception) would be stored in this future object.&lt;&#x2F;p&gt;
&lt;p&gt;Since there are &lt;code&gt;Future&lt;&#x2F;code&gt; objects involved (which you can &lt;code&gt;await&lt;&#x2F;code&gt; on), it can be
tempting to use this abstraction to write async code. But because now there are
multiple threads involved, it&#x27;s not that simple anymore. The functions being
submitted to the thread pool should now only make use of resources that are
thread-safe. In case two callables are submitted to the pool, both referencing a
particular object which is not thread-safe, there&#x27;s potential for weird
concurrency bugs.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;Closing thoughts - async is useful (and cool), but there is a time and place for
everything. It may result in an increased CPU utilization without necessarily
bringing speed improvements, so it&#x27;s helpful to keep that in mind when writing
async code.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>The “Hacker News Effect”</title>
        <published>2019-04-06T00:00:00+00:00</published>
        <updated>2019-04-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/the-hacker-news-effect/"/>
        <id>https://sgoel.dev/posts/the-hacker-news-effect/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/the-hacker-news-effect/">&lt;p&gt;I submitted &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;devtomanager.com&quot;&gt;Developer to Manager&lt;&#x2F;a&gt; to &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;news.ycombinator.com&#x2F;item?id=19485559&quot;&gt;Hacker News&lt;&#x2F;a&gt; on 25th March. The
common&#x2F;accepted knowledge is that there&#x27;s a ton of variation on what makes a
post hit the front page. Having absolutely no knowledge about those variations,
I submitted the post regardless, not thinking much about it. What happened next
was something I wasn&#x27;t expecting at all.&lt;&#x2F;p&gt;
&lt;p&gt;The post ended up trending on the front page for one full day, gathering
slightly more than 500 upvotes in the process, resulting in a cool 50,000
page views over the next 2 days, and moving the site&#x27;s Alexa rank by about 2
million places (up). It was quite an experience to see the number of concurrent
visitors to the site jump beyond 300.&lt;&#x2F;p&gt;
&lt;p&gt;The site is completely static and hosted on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.netlify.com&quot;&gt;Netlify&lt;&#x2F;a&gt;, so I wasn&#x27;t too worried
about all that traffic taking things down, but that&#x27;s besides the point.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;sgoel.dev&#x2F;posts&#x2F;the-hacker-news-effect&#x2F;developer-to-manager-fathom-analytics.png&quot; alt=&quot;Fathom Analytics&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I think the main takeaway for me, personally, was that it made me realize that I
was not the only one wishing for something like this to exist. I started working
on this project because I really felt the need for a resource like this, which
could help me with my own transition to a slightly more managerial role. Reading
the comments on hacker news, and the tons of encouraging emails that people sent
me, made it clear that there are plenty of other developers who are
transitioning to management and looking for resource to assist with the
transition. Solving personal problems is always good, but it&#x27;s even better when
others validate it.&lt;&#x2F;p&gt;
&lt;p&gt;So everyone who upvoted the HN post, left an encouraging comment, sent me an
email, volunteered for an interview for the site, or helped in any other way - a
huge thank you! I&#x27;ll be using all that motivation to make &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;devtomanager.com&quot;&gt;Developer to Manager&lt;&#x2F;a&gt;
&lt;strong&gt;the&lt;&#x2F;strong&gt; site you open when you want to know how to become a good engineering
manager.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Off by 1 (Day)</title>
        <published>2019-01-28T00:00:00+00:00</published>
        <updated>2019-01-28T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/off-by-1-day/"/>
        <id>https://sgoel.dev/posts/off-by-1-day/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/off-by-1-day/">&lt;p&gt;One of the most common bugs when writing software is the classic &quot;off by 1&quot;
error. In this post, I&#x27;ll talk about a similar bug I found in some code I
maintain, and how I fixed it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;backstory&quot;&gt;Backstory&lt;&#x2F;h2&gt;
&lt;p&gt;I maintain &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;beancount-dkb&quot;&gt;beancount-dkb&lt;&#x2F;a&gt;, which is a Python package that provides helper
classes for converting &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.dkb.de&#x2F;&quot;&gt;DKB&lt;&#x2F;a&gt; CSV exports to the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;furius.ca&#x2F;beancount&#x2F;&quot;&gt;Beancount&lt;&#x2F;a&gt; format. In
Beancount&#x27;s terminology, these &quot;helper&quot; classes are called &quot;importers&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re not familiar with Beancount, it&#x27;s a plain-text accounting tool which
lets you keep track of all your finances using plain text files. The idea is
that you maintain all your bank transactions in &lt;strong&gt;one&lt;&#x2F;strong&gt; text file, and then use
the tools that Beancount provides to run reports over all that data. The
transactions in this file follow the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Double-entry_bookkeeping_system&quot;&gt;Double Entry Accounting&lt;&#x2F;a&gt; method, and are
written in a DSL strictly specified by Beancount.&lt;&#x2F;p&gt;
&lt;p&gt;The way this works in practice is that every few weeks, you download
transactions from your bank (often this is a simple CSV export), and run them
through an importer to convert them into a data format that Beancount expects.
You then append the resulting data to a &lt;code&gt;.beancount&lt;&#x2F;code&gt; file you maintain which
contains &lt;strong&gt;all&lt;&#x2F;strong&gt; your transactions, going all the way back to stone age.
Finally, you use the suite of tools that Beancount provides to run all sorts of
analysis on your financial data.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s actually much less complicated than it sounds.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve used it to import my financial history from the last three years and the
whole process has been quite smooth, except for one hiccup. Balance assertions.
And that&#x27;s what this post is about.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;balance-assertions&quot;&gt;Balance Assertions&lt;&#x2F;h2&gt;
&lt;p&gt;Here&#x27;s a short code snippet that represents the history of a single bank
account, written in the Beancount DSL. The bank account is named &lt;code&gt;Assets:DKB&lt;&#x2F;code&gt;
and starts out with an opening balance of €100.&lt;&#x2F;p&gt;
&lt;p&gt;For simplicity, the history here consists of a single &quot;going to the supermarket&quot;
transaction.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;beancount&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;;; -*- mode: beancount -*-&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;; Date format - YYYY-MM-DD&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;option &amp;quot;title&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Max Mustermann&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;option &amp;quot;operating_currency&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;EUR&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2019&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01 open Assets&lt;&#x2F;span&gt;&lt;span&gt;:DKB&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2019&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01 open Equity&lt;&#x2F;span&gt;&lt;span&gt;:Opening-Balances&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2019&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01 open Expenses&lt;&#x2F;span&gt;&lt;span&gt;:Supermarket&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2019&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;15 *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Initialize Assets:DKB&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Assets&lt;&#x2F;span&gt;&lt;span&gt;:DKB&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;                           100.00&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Equity&lt;&#x2F;span&gt;&lt;span&gt;:Opening-Balances&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2019&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;15 *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Going to the supermarket&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Assets&lt;&#x2F;span&gt;&lt;span&gt;:DKB&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;                          -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;30.00&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Expenses&lt;&#x2F;span&gt;&lt;span&gt;:Supermarket&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;                 30.00&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2019&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;16 balance Assets&lt;&#x2F;span&gt;&lt;span&gt;:DKB&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;            70.00&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The transaction with the description &quot;Going to the supermarket&quot; shows that the
owner went to some supermarket and spent €30, which means that the account has
€70 left at the end.&lt;&#x2F;p&gt;
&lt;p&gt;The interesting bit here is the last line.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;beancount&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2019&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;16 balance Assets&lt;&#x2F;span&gt;&lt;span&gt;:DKB&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;            70.00&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This line instructs Beancount to &lt;strong&gt;assert&lt;&#x2F;strong&gt; that the balance of the given
account is the given amount at the beginning of the given date. And in case
that&#x27;s not true, Beancount should refuse to process things any further because
there&#x27;s obviously something wrong with the data.&lt;&#x2F;p&gt;
&lt;p&gt;Such assertions are not completely necessary, but having them gives you the
peace of mind that the data you&#x27;re working with is not wrong. This can happen in
case of something like duplicate transactions. Often, when you have two accounts
and you transfer money from one account to the other one, the same transaction
is going to show up in both the account summaries. For Beancount they are two
different transactions, but practially speaking that&#x27;s not true. They are two
legs of the same transaction. Left unmerged, these would result in wrong numbers
on both the accounts. This is why balance assertions come in handy.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-bug&quot;&gt;The Bug&lt;&#x2F;h2&gt;
&lt;p&gt;When I first started out with Beancount, there were no balance assertions in my
data. I knew the concept, but the initial versions of &lt;code&gt;beancount-dkb&lt;&#x2F;code&gt; I released
didn&#x27;t output any &lt;code&gt;balance&lt;&#x2F;code&gt; directives.&lt;&#x2F;p&gt;
&lt;p&gt;After a few months of regular Beancount usage and realizing how useful these
assertions can be, I decided to implement support in &lt;code&gt;beancount-dkb&lt;&#x2F;code&gt;. This was
not too much work since the documentation is pretty clear on how to do this.&lt;&#x2F;p&gt;
&lt;p&gt;So something like this,&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;csv&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;quot;01.01.2019&amp;quot;;&amp;quot;&amp;quot;;&amp;quot;&amp;quot;;&amp;quot;&amp;quot;;&amp;quot;Tagessaldo&amp;quot;;&amp;quot;&amp;quot;;&amp;quot;&amp;quot;;&amp;quot;100,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;00&amp;quot;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;... becomes this&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;beancount&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2019&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01 balance Assets&lt;&#x2F;span&gt;&lt;span&gt;:DKB&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;            100.00&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;What turned out to be more work was testing the whole thing.&lt;&#x2F;p&gt;
&lt;p&gt;After the initial implementation, I noticed the numbers just weren&#x27;t adding up.
The test cases were fine, but the test cases were made-up data anyway. The
output on the actual data just didn&#x27;t add up. After a few hours of trying to
figure things out, I found this little gem in the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.google.com&#x2F;document&#x2F;d&#x2F;1wAMVrKIA2qtRGmoVDSUBJGmYZSygUaR0uOMW1GV3YE0&#x2F;edit#heading=h.l0pvgeniwvq8&quot;&gt;documentation&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that a balance assertion, like all other non-transaction directives,
applies at the beginning of its date (i.e., midnight at the start of day).
Just imagine that the balance check occurs right after midnight on that day.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;The difference is subtle, but can easily lead to numbers not adding up.&lt;&#x2F;p&gt;
&lt;p&gt;What&#x27;s happening here is that Beancount is expecting the balance amount to be
valid at the &lt;strong&gt;beginning&lt;&#x2F;strong&gt; of the day, while the balance values from the DKB
output correspond to the amount at the &lt;strong&gt;end&lt;&#x2F;strong&gt; of the day. Note that the DKB
behavior is not documented anywhere (and if it is, I couldn&#x27;t find the relevant
docs), but from all the data I saw, this makes the most sense.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;The fix in this case was easy. Just set the date of the &lt;code&gt;balance&lt;&#x2F;code&gt; directive to 1
day after what&#x27;s in the CSV. This shifts the time of the assertion to midnight
at the start of the next day, which in turn makes the numbers all look good.&lt;&#x2F;p&gt;
&lt;p&gt;Admittedly, this bug wasn&#x27;t too hairy and the fix wasn&#x27;t that tricky either. But
often in cases like this where things are rather subtle, it can take you
anywhere between a few minutes to a few hours to find a fix. For me, it was
somewhere in between.&lt;&#x2F;p&gt;
&lt;p&gt;The latest release of &lt;code&gt;beancount-dkb&lt;&#x2F;code&gt; includes this patch. I have (more or less
completely) rewritten my Beancount data using the latest code with the correct
balance assertions and so far things have been smooth. Apologies if an older
version affected your data!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Developer to Manager</title>
        <published>2018-12-22T00:00:00+00:00</published>
        <updated>2018-12-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/developer-to-manager/"/>
        <id>https://sgoel.dev/posts/developer-to-manager/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/developer-to-manager/">&lt;p&gt;&lt;strong&gt;TL;DR&lt;&#x2F;strong&gt; I&#x27;ve been working on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;devtomanager.com&quot;&gt;Developer to Manager&lt;&#x2F;a&gt; for the last few weeks.
It&#x27;s a collection of interviews with software developers who moved to
management, and explores what they learned, the best advice they received on
this topic, and so on. Check it out!&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;In the software industry, the progression from being a &quot;software developer&quot; to
being an &quot;engineering manager&quot; is considered fairly normal. Well performing
developers are often &quot;promoted&quot; to a role where they are not writing code
anymore, but are instead responsible for managing a team of developers.&lt;&#x2F;p&gt;
&lt;p&gt;As a software developer, you write&#x2F;review (at least a little bit of) code every
single day. You&#x27;re fixing bugs, implementing product features, pushing commits,
reviewing&#x2F;merging pull requests, you know the drill. Your life basically
revolves around code.&lt;&#x2F;p&gt;
&lt;p&gt;As an engineering manager though, most of this changes. Depending on your
company, you might still write code every now and then, but it won&#x27;t be your
main job. Your main job now is to support a team of developers and figure out
how to juggle everything to be able to add maximum value to the business you&#x27;re
working for. Sounds vague? Yes, it is. You are now responsible for shielding
your developers from external factors, aligning their career goals with the
daily work they do at your company, regular one-on-ones with them to make sure
things are going alright, some other responsibilities you don&#x27;t know about yet,
and a few more responsibilities you don&#x27;t even know that you don&#x27;t know.&lt;&#x2F;p&gt;
&lt;p&gt;In a nutshell though, your life is now revolving around people.&lt;&#x2F;p&gt;
&lt;p&gt;If the two roles are that different, is a &quot;developer -&amp;gt; manager&quot; transition
really a promotion?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-back-story&quot;&gt;The Back Story&lt;&#x2F;h2&gt;
&lt;p&gt;About two years ago, I co-founded a startup with a few friends, where initially
I was the only person responsible for software development. We did bring a few
other people on board later who now handle a major chunk of work, but the team
is still quite small.&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, when you&#x27;re the only developer co-founding a startup, you&#x27;re often
considered the &quot;face of software&quot; for the people doing business development.
This additional responsibility requires you to do a lot more than just write
code.  Additionally, since you&#x27;re personally vested in the success of the
company, you&#x27;re not in the &quot;employee mindset&quot; anymore, which means you can&#x27;t
just sit back and wait for someone else to give you things to work on. You need
to interface with business development, make sure that software is being written
smoothly, there are no bottlenecks, the developers working along with you are
happy, their career goals are being met, you&#x27;re meeting deadlines, and so much
more.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s a very delicate role, and depending on what makes you tick, it can either
be the best thing to happen to your career or the worst. I personally don&#x27;t
think there&#x27;s anything in the middle.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-motivation&quot;&gt;The Motivation&lt;&#x2F;h2&gt;
&lt;p&gt;When I found myself in a similar role (which was way outside my comfort zone
btw), I realized I was both excited and apprehensive at the same time.&lt;&#x2F;p&gt;
&lt;p&gt;Excited, because this is an opportunity that allows you to impact your
organization like you could have never imagined. Such a position requires you to
step out of the &quot;coder in the corner&quot; mindset and try to figure out how to help
the business succeed, and then go ahead and do all of it. The leverage you have
here is immense.&lt;&#x2F;p&gt;
&lt;p&gt;Apprehensive, because with great power comes great responsibility. And how do
you assume responsibility without knowing much about what you&#x27;re getting into?&lt;&#x2F;p&gt;
&lt;p&gt;With this chain of thought, I started looking for resources to prepare myself to
handle things better. I like learning from books, so my first instinct was to
search for books on this topic. It&#x27;s difficult to sift through the typical &quot;how
to become a manager in 24 hours&quot; noise, but I managed to find two really good
books - &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.goodreads.com&#x2F;book&#x2F;show&#x2F;33369254-the-manager-s-path&quot;&gt;The Manager&#x27;s Path&lt;&#x2F;a&gt;, and &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.goodreads.com&#x2F;book&#x2F;show&#x2F;1317946.Managing_Humans&quot;&gt;Managing Humans&lt;&#x2F;a&gt;. Both of them are written
from the point of view of a software developer, and are highly recommended
reading if you&#x27;re interested in this domain.&lt;&#x2F;p&gt;
&lt;p&gt;To my surprise though, I couldn&#x27;t find that many more resources. Maybe my
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;duckduckgo.com&quot;&gt;DuckDuckGo&lt;&#x2F;a&gt; skills are getting rusty, but I found it frustrating that there
aren&#x27;t that many good resources written from a developer point of view,
especially given how crucial such a role can be to a software company.&lt;&#x2F;p&gt;
&lt;p&gt;What I would have &lt;strong&gt;really&lt;&#x2F;strong&gt; liked to have when starting out, was to just know
how others handled it. Nothing fancy, but just having some other engineering
managers answer a common set of questions about their transition so I could get
a sense of how best to prepare myself for something like that.&lt;&#x2F;p&gt;
&lt;p&gt;I wasn&#x27;t looking for an exact set of steps I should take to become a good
manager (not to imply that something like that exists), but just knowing that
someone else has been in your shoes and managed to come out successful goes a
long way, and helps in a way no article&#x2F;book&#x2F;teacher probably can.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-process&quot;&gt;The Process&lt;&#x2F;h2&gt;
&lt;p&gt;Since I couldn&#x27;t find something like that, I decided to try building it on my
own.&lt;&#x2F;p&gt;
&lt;p&gt;I started by contacting a few of my manager friends and talked to them on this
topic. After almost all of them agreed with the premise, I realized that the
idea cannot be completely stupid and that there could be something worth
exploring here.&lt;&#x2F;p&gt;
&lt;p&gt;I then brain-stormed a short list of questions that I would have liked to have
other engineering managers answer, and then emailed it to a few of my friends
and a few more people I found on the web. The idea was to have different people
answer a common set of questions so that the readers can get different
perspectives on the same topics.&lt;&#x2F;p&gt;
&lt;p&gt;The response I received was extremely encouraging. People were more than willing
to invest the time into talking about their transitions to help younger
developers make this transition successfully. And there was a ton of stuff to
explore here - best practices, things to keep in mind, things to avoid doing,
and much more.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-product&quot;&gt;The Product&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;devtomanager.com&quot;&gt;Developer to Manager&lt;&#x2F;a&gt; is what has come out of all this.&lt;&#x2F;p&gt;
&lt;p&gt;The site currently hosts a collection of interviews with software developers who
moved to management. I ask them a few basic questions about their background,
what their transition felt like, what their takeaways were, and so on. As I
mentioned before, the idea is to get different perspectives on similar
situations.&lt;&#x2F;p&gt;
&lt;p&gt;I try to publish one interview every week, but of course this depends on the
availability of interviewees.&lt;&#x2F;p&gt;
&lt;p&gt;In the longer term I would like to see &quot;Developer to Manager&quot; evolve into
&lt;strong&gt;the&lt;&#x2F;strong&gt; website you open when you want to learn more about how to become a good
engineering manager.&lt;&#x2F;p&gt;
&lt;p&gt;How do I plan to do that? I don&#x27;t know yet. There are a ton of things floating
around in my head right now which don&#x27;t necessarily fit together, but I have the
feeling that given enough time it should be possible to incorporate all of them
into the product.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>The most dynamic static site you&#x27;ll ever see</title>
        <published>2018-10-27T00:00:00+00:00</published>
        <updated>2018-10-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/the-most-dynamic-static-site-youll-ever-see/"/>
        <id>https://sgoel.dev/posts/the-most-dynamic-static-site-youll-ever-see/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/the-most-dynamic-static-site-youll-ever-see/">&lt;p&gt;I&#x27;m not completely convinced by the current landscape of static site generators.
Most of these don&#x27;t generate &quot;sites&quot;. They generate &quot;blogs&quot;, and the concept of
&quot;pages&quot; and &quot;posts&quot; is so deeply ingrained in the implementation that it&#x27;s
difficult to break free of that structure. Eventually this starts showing up in
your static &quot;site&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;The point is, I&#x27;m missing flexibility.&lt;&#x2F;p&gt;
&lt;p&gt;I started working on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;devtomanager.com&quot;&gt;Developer to Manager&lt;&#x2F;a&gt; recently, and right from the
beginning on I wanted it to be a static site (which it is). That being said, I
still had some pretty dynamic features planned for it so I wanted a tool that
could help me do that.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;mvc-enabled-static-sites&quot;&gt;MVC enabled static sites&lt;&#x2F;h2&gt;
&lt;p&gt;Luckily I found &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;getstatik.com&#x2F;&quot;&gt;statik&lt;&#x2F;a&gt; which solved most of my problems. The idea behind
&lt;code&gt;statik&lt;&#x2F;code&gt; is pretty neat and as far as I understand, it directly maps to the MVC
pattern.&lt;&#x2F;p&gt;
&lt;p&gt;The models are defined using YAML files and the actual site data is written in
Markdown.&lt;&#x2F;p&gt;
&lt;p&gt;You then define &quot;views&quot;, which in my mind map to MVC controllers. These are YAML
files specifying page metadata - things like the models that this page is
supposed to render, which template to pick, which URL to render at, and so on.&lt;&#x2F;p&gt;
&lt;p&gt;Finally, you define templates which are standard &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;jinja.pocoo.org&#x2F;&quot;&gt;Jinja&lt;&#x2F;a&gt; files and are rendered
using the views config.&lt;&#x2F;p&gt;
&lt;p&gt;This setup worked fairly well, until I started running into special cases. At
that point I tried finding alternatives which would let me have more flexibility
than what &lt;code&gt;statik&lt;&#x2F;code&gt; offered and still let me have a static site at the end.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;freezing-flask-applications&quot;&gt;Freezing Flask applications&lt;&#x2F;h2&gt;
&lt;p&gt;Even though I couldn&#x27;t find a convincing alternative, what I did find was
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pythonhosted.org&#x2F;Frozen-Flask&#x2F;&quot;&gt;Frozen-Flask&lt;&#x2F;a&gt;. The idea is that it lets you &quot;freeze&quot; your &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;flask.pocoo.org&#x2F;&quot;&gt;Flask&lt;&#x2F;a&gt; application
to a set of static HTML files. So you basically build a normal web app and then
ask &lt;code&gt;Frozen-Flask&lt;&#x2F;code&gt; to compile the site into plain HTML which you can deploy
anywhere you like.&lt;&#x2F;p&gt;
&lt;p&gt;This setup is really nice, but with only one disadvantage - defining your site
data is still not convenient. You have to go through the hassle of setting up a
data store (like SQLite or PostgreSQL). And while these data stores do the job
extremely well, it&#x27;s more or less overkill for the purpose of a static website.&lt;&#x2F;p&gt;
&lt;p&gt;Besides, depending on how many data models you have and what types they contain,
this can quickly get out of hand. Imagine writing an &lt;code&gt;INSERT&lt;&#x2F;code&gt; statement for a
blog post. In addition, you can&#x27;t version control your data. I mean, technically
you can, but the diffs won&#x27;t make any sense to a human.&lt;&#x2F;p&gt;
&lt;p&gt;The only missing link here seemed to be the ability to define data models and
enter data like a human. So I spent a weekend working on this, and
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;flask-filealchemy&quot;&gt;Flask-FileAlchemy&lt;&#x2F;a&gt; is what came out.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;flask-filealchemy&quot;&gt;Flask-FileAlchemy&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;code&gt;Flask-FileAlchemy&lt;&#x2F;code&gt; is a Python package that lets you define your static data in
plain text files using a combination of YAML and Markdown.&lt;&#x2F;p&gt;
&lt;p&gt;Why plain text files? Because plain text has the advantage that it&#x27;s much easier
to handle for a human. And there&#x27;s the added advantage that you can check these
files into source control so that your application code and the data both have a
common history.&lt;&#x2F;p&gt;
&lt;p&gt;So how does it work?&lt;&#x2F;p&gt;
&lt;p&gt;You start with a normal &lt;code&gt;Flask&lt;&#x2F;code&gt; app and define your data models using
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;flask-sqlalchemy.pocoo.org&#x2F;&quot;&gt;Flask-SQLAlchemy&lt;&#x2F;a&gt;. You then add a directory somewhere on disk which acts like
your data &quot;storage&quot; and contains a subdirectory for each model you&#x27;ve defined.
&lt;code&gt;Flask-FileAlchemy&lt;&#x2F;code&gt; then loads all the data from these subdirectories, puts them
into whatever data store you&#x27;re using (in-memory SQLite is recommended), and
makes it all available for your app to query via the standard &lt;code&gt;Flask-SQLAlchemy&lt;&#x2F;code&gt;
session interface.&lt;&#x2F;p&gt;
&lt;p&gt;This lets you retain the comfort of dynamic sites without compromising on the
simplicity of static files.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;walkthrough&quot;&gt;Walkthrough&lt;&#x2F;h2&gt;
&lt;p&gt;Let&#x27;s walk through a simple example to show how it really works.&lt;&#x2F;p&gt;
&lt;p&gt;Step 1, define your app and the associated &lt;code&gt;SQLAlchemy&lt;&#x2F;code&gt; models.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;app&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; Flask(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;__name__&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# configure Flask-SQLAlchemy&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;app.config[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;SQLALCHEMY_DATABASE_URI&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;sqlite:&#x2F;&#x2F;&#x2F;:memory:&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;db&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; SQLAlchemy(app)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; BlogPost&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;db&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Model&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   __tablename__&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;blog_posts&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   slug&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; Column(String(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;255&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; primary_key&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;True&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; Column(String(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;255&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; nullable&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;False&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   contents&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; Column(Text,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; nullable&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;False&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After this, create a &lt;code&gt;data&#x2F;&lt;&#x2F;code&gt; directory somewhere on your disk. Under this
directory, create a &lt;code&gt;blog_posts&lt;&#x2F;code&gt; directory for storing all the blog posts. In
general, this &lt;code&gt;data&#x2F;&lt;&#x2F;code&gt; directory is supposed to contain one directory per model,
with the same name as the model&#x27;s &lt;code&gt;__tablename__&lt;&#x2F;code&gt; attribute.&lt;&#x2F;p&gt;
&lt;p&gt;In this example, we&#x27;ll create a single blog post by entering the following
contents in the &lt;code&gt;data&#x2F;blog_posts&#x2F;first-post-ever.yml&lt;&#x2F;code&gt; file.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;slug&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; first-post-ever&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;title&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; First post ever!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;contents&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;  This blog post talks about how it&amp;#39;s the first post ever!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Finally, configure &lt;code&gt;Flask-FileAlchemy&lt;&#x2F;code&gt; with this setup and ask it to load all
your data.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# configure Flask-FileAlchemy&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;app.config[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;FILEALCHEMY_DATA_DIR&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; os.path.join(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   os.path.dirname(os.path.realpath(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;__file__&lt;&#x2F;span&gt;&lt;span&gt;)),&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;data&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;app.config[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;FILEALCHEMY_MODELS&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; (BlogPost,)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# load tables&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;FileAlchemy(app, db).load_tables()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;Flask-FileAlchemy&lt;&#x2F;code&gt; then loads all the data under that directory and stores it
in the data store of your choice that you configured (again, the preference
being in-memory SQLite). You can then use &lt;code&gt;db.session&lt;&#x2F;code&gt; to query all this data
like you would in any normal application.&lt;&#x2F;p&gt;
&lt;p&gt;At this point, you would have a fully functioning Flask app running locally, on
which you can just run &lt;code&gt;Frozen-Flask&lt;&#x2F;code&gt; to get a bunch of HTML files.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;m obviously biased, but I do think the result is quite neat and useful. I
don&#x27;t see myself switching away from the existing static site generators (like
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;blog.getpelican.com&#x2F;&quot;&gt;Pelican&lt;&#x2F;a&gt;) any time soon for general purpose blogs. Mostly because at this point
a lot of people have contributed a lot of effort into building and maintaining
these packages and it&#x27;s hard to beat that quality, let alone gaining feature
parity.&lt;&#x2F;p&gt;
&lt;p&gt;At the same time, for sites that are slightly more complicated or require a bit
more flexibility than what a blog generator would give you, I definitely see
myself using &lt;code&gt;Flask-FileAlchemy&lt;&#x2F;code&gt; in the future. &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;devtomanager.com&quot;&gt;Developer to Manager&lt;&#x2F;a&gt; is
already running using this stack, and I think that this package is useful enough
to build more non-trivial static sites.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve released the module on PyPI under the name &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;flask-filealchemy&quot;&gt;flask-filealchemy&lt;&#x2F;a&gt;. Hope it&#x27;s
useful!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Setting up your Python open source project</title>
        <published>2018-10-03T00:00:00+00:00</published>
        <updated>2018-10-03T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/setting-up-your-python-open-source-project/"/>
        <id>https://sgoel.dev/posts/setting-up-your-python-open-source-project/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/setting-up-your-python-open-source-project/">&lt;p&gt;In the past few months I&#x27;ve been able to find the time and energy to open-source
5 Python projects on Github. I think being able to work remotely from home on my
own schedule is &lt;strong&gt;the&lt;&#x2F;strong&gt; reason something like this could have happened, but
that&#x27;s for a different blog post.&lt;&#x2F;p&gt;
&lt;p&gt;In this post, I would like to go over a few tools you can (and probably should!)
set up on your own projects so that they stay in good shape. Some of them are
Github-integrations and some are tools you run locally.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;travis-ci&quot;&gt;Travis CI&lt;&#x2F;h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Code without tests is broken by design.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Having a well-maintained test-suite in your project has a lot of advantages.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Tests provide a fairly automated way to repeatedly make sure that your code
is doing what it&#x27;s supposed to do. Without tests, all you have is some source
code which you &lt;strong&gt;believe&lt;&#x2F;strong&gt; works, but you can&#x27;t prove it.&lt;&#x2F;li&gt;
&lt;li&gt;It helps you make changes to your codebase &lt;strong&gt;confidently&lt;&#x2F;strong&gt;. Let&#x27;s say
tomorrow morning you decide to change a few things. A good test-suite would
help you assert that the behavior of your code post-surgery is the same as it
was before.&lt;&#x2F;li&gt;
&lt;li&gt;Tests also act as a form of documentation. Often, when I&#x27;m jumping into a new
project and I don&#x27;t know where to start, I open up the test-suite and start
reading through the tests to try to get a sense of what exactly is going on.
The tests point me to which functions or classes or modules are important,
which in turn gives me a few entry points to the source code that I could use
to dive in.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;So now that we&#x27;ve established the importance of having tests, it also makes
sense to run them on every change you make. This is what continuous integration
is, and this is where &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;travis-ci.org&quot;&gt;Travis CI&lt;&#x2F;a&gt; comes in.&lt;&#x2F;p&gt;
&lt;p&gt;Travis is a free and very popular continuous integration system. Once you enable
it on a project of yours, it&#x27;ll run your test suite on every single commit you
make and tell you whether or not something broke.&lt;&#x2F;p&gt;
&lt;p&gt;Setting Travis up is quite straight-forward. When you sign-up (it&#x27;s free for
open-source projects), it shows you a list of projects it found in your Github
account. You can then decide to enable selected projects from that list.&lt;&#x2F;p&gt;
&lt;p&gt;Once you&#x27;ve done that, it&#x27;s just a matter of committing a &lt;code&gt;.travis.yml&lt;&#x2F;code&gt; config
file to tell Travis more about what kind of a project you&#x27;re trying to build.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s an example config file for a simple Python project.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;language&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; python&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;python&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;3.5&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;3.6&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;install&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; pip install -r requirements.txt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;script&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; python setup.py test&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This tells Travis that you&#x27;re building a Python project, and you&#x27;d like to test
it with Python versions 3.5 and 3.6. The &lt;code&gt;install&lt;&#x2F;code&gt; step lets you install any
project dependencies that your test suite might require. And finally the
&lt;code&gt;script&lt;&#x2F;code&gt; step is how you let Travis know how to run your test suite.&lt;&#x2F;p&gt;
&lt;p&gt;From this point on, running all your tests for every single commit you make is
the responsibility of Travis. Pretty neat.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;pyup&quot;&gt;PyUp&lt;&#x2F;h2&gt;
&lt;p&gt;More often than not, your project depends on some other software. Newer versions
of these dependencies marks the ones your project is using outdated. This leaves
you depending on older versions which could potentially contain security
vulnerabilities.&lt;&#x2F;p&gt;
&lt;p&gt;Ideally you would like to be notified when a dependency has a new release. You
could either keep an eye on the project pages for new releases, or you could
sign up for email notifications.&lt;&#x2F;p&gt;
&lt;p&gt;The problem is that these solutions don&#x27;t scale all that well. There&#x27;s a limited
number of changelogs you can stay on top of. And let&#x27;s not talk about email
notifications.&lt;&#x2F;p&gt;
&lt;p&gt;This is where &lt;a href=&quot;htts:&#x2F;&#x2F;pyup.io&quot;&gt;PyUp&lt;&#x2F;a&gt; comes in.&lt;&#x2F;p&gt;
&lt;p&gt;PyUp is a Python security and dependency tracker that&#x27;s free for open-source
projects.&lt;&#x2F;p&gt;
&lt;p&gt;Once you signup and enable it on a few projects, it&#x27;ll start watching your
project dependencies. If it notices an out-of-date dependency, it will open a
pull-request with an update. All you have to do is review and approve such PRs.&lt;&#x2F;p&gt;
&lt;p&gt;What works even better is enabling both Travis and PyUp. In such cases, Travis
will test every PR submitted by PyUp for potential breakages. This makes your
decision to merge (or reject) even easier.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;isort&quot;&gt;isort&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;timothycrosley&#x2F;isort&quot;&gt;isort&lt;&#x2F;a&gt; is a Python library to sort imports alphabetically. If your source files
have a ton of imports, it can be really helpful to organize them in sections and
then sort them alphabetically so they&#x27;re easy to visually parse.&lt;&#x2F;p&gt;
&lt;p&gt;For example, if you have a Python file that looks like the following.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; my_lib&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; Object&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;Hey&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; os&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; my_lib&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; Object3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; my_lib&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; Object2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; sys&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; third_party&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; lib15, lib1, lib2, lib3, lib4, lib5, lib6, lib7, lib8, lib9, lib10, lib11, lib12, lib13, lib14&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; sys&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; __future__&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; absolute_import&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; third_party&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; lib3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;yo&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Running &lt;code&gt;isort&lt;&#x2F;code&gt; on it produces the following.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; __future__&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; absolute_import&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; os&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; sys&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; third_party&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; (lib1, lib2, lib3, lib4, lib5, lib6, lib7, lib8,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                         lib9, lib10, lib11, lib12, lib13, lib14, lib15)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; my_lib&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; Object, Object2, Object3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;Hey&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;yo&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;By default, &lt;code&gt;isort&lt;&#x2F;code&gt; sorts your imports into roughly three sections - the
standard library, third party modules, and then the modules from the current
project.&lt;&#x2F;p&gt;
&lt;p&gt;This, and a few other settings can be controlled using a configuration file. The
standard way is to add a &lt;code&gt;.isort.cfg&lt;&#x2F;code&gt; file in your project root, but you can
also add a &lt;code&gt;[isort]&lt;&#x2F;code&gt; section to your project&#x27;s &lt;code&gt;tox.ini&lt;&#x2F;code&gt; or &lt;code&gt;setup.cfg&lt;&#x2F;code&gt; files
and it&#x27;ll all work the same way. &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;django&#x2F;django&#x2F;blob&#x2F;master&#x2F;setup.cfg&quot;&gt;Django&lt;&#x2F;a&gt; for example specifies its isort
settings inside &lt;code&gt;setup.cfg&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;A very useful feature of &lt;code&gt;isort&lt;&#x2F;code&gt; is checking your source files for incorrectly
formatted imports. If you run &lt;code&gt;isort **&#x2F;*.py -c -vb&lt;&#x2F;code&gt;, isort will display a
report of correctly&#x2F;incorrectly formatted files on stderr, and exit with an
appropriate exit code.&lt;&#x2F;p&gt;
&lt;p&gt;This makes it very suitable as a build step in Travis. If you add &lt;code&gt;isort **&#x2F;*.py -c -vb&lt;&#x2F;code&gt; as another line in the &lt;code&gt;script&lt;&#x2F;code&gt; section of your travis config, Travis
will make the build as red if your source code contains incorrectly formatted
imports.&lt;&#x2F;p&gt;
&lt;p&gt;One might think it&#x27;s not worth the effort to sort the imports a certain way. But
I personally find it nice when a codebase looks like it was written by &lt;strong&gt;one&lt;&#x2F;strong&gt;
person even though multiple people may have worked on it. It makes you feel that
things are consistent. Module imports may be a rather small section in your
source code, but it&#x27;s the first thing you see when opening a file, so having a
certain order there certainly goes a long way in making things look consistent.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;In this post I talked about 3 integrations you can enable on your Python code to
make it nicer to work with. This is really only the tip of the iceberg and there
are hundreds, if not thousands, of more such packages out there aiming at
improving your code quality.&lt;&#x2F;p&gt;
&lt;p&gt;A lot of Python code out there stresses a lot on code quality, so that&#x27;s
definitely a metric I as a software developer would advice optimizing on (apart
from other reasons).&lt;&#x2F;p&gt;
&lt;p&gt;It can sometimes feel like extra effort to set these external tools&#x2F;integrations
up. In the long term, however, this effort is completely worth it. And enabling
integrations like the ones I talked about in this post will go a long way in
making your code look &lt;strong&gt;Pythonic&lt;&#x2F;strong&gt; (for some definition of that word).&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Managing personal finances using Python</title>
        <published>2018-09-16T00:00:00+00:00</published>
        <updated>2018-09-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/managing-personal-finances-using-python/"/>
        <id>https://sgoel.dev/posts/managing-personal-finances-using-python/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/managing-personal-finances-using-python/">&lt;p&gt;📚 &lt;strong&gt;Update&lt;&#x2F;strong&gt;: This is now an ebook! Check out
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;personalfinancespython.com&quot;&gt;https:&#x2F;&#x2F;personalfinancespython.com&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;Have you ever been in a situation where you find yourself wondering at the end
of the month where hundreds of Euros (or $CURRENCY) basically just &quot;disappeared&quot;
and you have no idea where it all went?&lt;&#x2F;p&gt;
&lt;p&gt;I know I have been. It really does not make a difference what your income is.
With the amount of marketing-speak around you encouraging you every single
moment to &quot;buy more&quot;, you inevitably end up doing just that. It also doesn&#x27;t
help that buying something now is as easy as clicking a few buttons on Amazon.
It does let you buy things conveniently, but that convenience has a hidden
price. And that hidden price adds up quickly, the more you buy.&lt;&#x2F;p&gt;
&lt;p&gt;So what&#x27;s the solution? Well, the best thing is to spend less (obviously). But
the second best thing (which is also a bit more realistic) is to &lt;strong&gt;know what
you&#x27;re spending on&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Early this year I found myself in a similar situation. At the end of the month,
I would look at my balance and had no idea how much of that money (which was not
there) was spent on eating out, rent, car, and so on. I wanted to track where
all that money was going, and which spendings were not &lt;strong&gt;absolutely&lt;&#x2F;strong&gt; necessary.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;solution-space&quot;&gt;Solution Space&lt;&#x2F;h2&gt;
&lt;p&gt;Once you&#x27;ve identified the problem, there are plenty of solutions in the
market. There are tools which you can buy (🤷‍♂️) that integrate with your
bank, do their analysis, and show you shiny graphs on what you have been
spending the most on and where you could save.&lt;&#x2F;p&gt;
&lt;p&gt;The problem is, that most of these tools are either proprietary or
cloud-hosted.&lt;&#x2F;p&gt;
&lt;p&gt;Cloud-hosted is basically a no-go. I honestly don&#x27;t care what the privacy policy
says or what&#x27;s in the security whitepaper. I&#x27;ve worked at enough software
companies and talked to enough software developers to know how things really
work.&lt;&#x2F;p&gt;
&lt;p&gt;Proprietary is something I &lt;strong&gt;could&lt;&#x2F;strong&gt; consider, but not a single one of those
tools supported the German banks I have my accounts with. Oh, and the fact that
I&#x27;m using Linux as my daily operating system doesn&#x27;t help either.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;plain-text-accounting&quot;&gt;Plain Text Accounting&lt;&#x2F;h2&gt;
&lt;p&gt;Then I discovered &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;plaintextaccounting.org&#x2F;&quot;&gt;Plain Text Accounting&lt;&#x2F;a&gt;. The concept is simple - you maintain
a list of all your transactions in a plain text file, which &lt;strong&gt;you&lt;&#x2F;strong&gt; are
responsible for curating. So every single time you make a transaction that
involves money, you record it in this file. This list of transactions follows
the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Double-entry_bookkeeping_system&quot;&gt;Double Entry Accounting&lt;&#x2F;a&gt; method, and is written in a domain specific
language which may vary slightly from tool to tool.&lt;&#x2F;p&gt;
&lt;p&gt;I found this workflow quite appealing. It basically checked off every single
thing on the list of my requirements.&lt;&#x2F;p&gt;
&lt;p&gt;✔️ Simple&lt;&#x2F;p&gt;
&lt;p&gt;✔️ Open Source&lt;&#x2F;p&gt;
&lt;p&gt;✔️ Runs on Linux&lt;&#x2F;p&gt;
&lt;p&gt;✔️ No data in the cloud (unless you really want)&lt;&#x2F;p&gt;
&lt;p&gt;There are multiple implementations of this method in multiple languages -
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.ledger-cli.org&#x2F;&quot;&gt;ledger&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;hledger.org&#x2F;&quot;&gt;hledger&lt;&#x2F;a&gt;, and &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;furius.ca&#x2F;beancount&quot;&gt;beancount&lt;&#x2F;a&gt; to name the most popular ones. I settled on
using &lt;code&gt;beancount&lt;&#x2F;code&gt; simply because it also checked the fourth Python checkbox 🐍.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;double-entry-accounting&quot;&gt;Double Entry Accounting&lt;&#x2F;h2&gt;
&lt;p&gt;Double Entry Accounting is a fairly simple idea, but it can be slightly tricky
to really &lt;strong&gt;get&lt;&#x2F;strong&gt;. It&#x27;s based around the concept of &quot;accounts&quot; and &quot;transactions
between accounts&quot;, and the core of it is the following constraint.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;In a transaction, the sum total of all the amounts posted to all the accounts
must be zero.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;As an example, consider the following transaction.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;beancount&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2018&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;15 *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Lastschrift REWE SAGT DANKE. 12345678&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Assets&lt;&#x2F;span&gt;&lt;span&gt;:Commerzbank:EC&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;               -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;31.24&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Expenses&lt;&#x2F;span&gt;&lt;span&gt;:Supermarket&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;                 31.24&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Here, an amount of 31.24 EUR is being posted to the &quot;Supermarket&quot; account, and a
corresponding -31.24 EUR to the main Commerzbank account so that the sum total of both
these amounts is 0.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;beancount&quot;&gt;Beancount&lt;&#x2F;h2&gt;
&lt;p&gt;Beancount is a set of tools that lets you keep track of your finances using the
double-entry method.&lt;&#x2F;p&gt;
&lt;p&gt;The idea is that you record all your transactions in a plain text file.
Beancount then reads all those transactions and lets you assess your finances in
any way that you can imagine. Not only can it tell you what all you&#x27;ve been
spending money on and where all your money is, but also where all your money has
ever been at any given point of time since you opened your account.&lt;&#x2F;p&gt;
&lt;p&gt;In addition, it provides a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.google.com&#x2F;document&#x2F;d&#x2F;1s0GOZMcrKKCLlP29MD7kHO4L88evrwWdIO0p4EwRBE0&#x2F;edit&quot;&gt;SQL-like query language&lt;&#x2F;a&gt; which you can use to write
arbitrary SQL queries over your financial history. With this in place, there&#x27;s
really no limit on what analysis you can make. It&#x27;s quite powerful.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s a basic example of something that Beancount can work with.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;beancount&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;;; -*- mode: beancount -*-&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;; Date format - YYYY-MM-DD&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;option &amp;quot;title&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;John Doe&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;option &amp;quot;operating_currency&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;EUR&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2018&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01 open Assets&lt;&#x2F;span&gt;&lt;span&gt;:Commerzbank:EC&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2018&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01 open Expenses&lt;&#x2F;span&gt;&lt;span&gt;:Supermarket&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2018&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;01&lt;&#x2F;span&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;15 *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Lastschrift REWE SAGT DANKE. 12345678&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Assets&lt;&#x2F;span&gt;&lt;span&gt;:Commerzbank:EC&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;               -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;31.24&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    Expenses&lt;&#x2F;span&gt;&lt;span&gt;:Supermarket&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;                 31.24&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; EUR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The first few lines with the &lt;code&gt;option&lt;&#x2F;code&gt; directives tell Beancount some metadata
(what the title of this workspace is and that the working currency is EUR).&lt;&#x2F;p&gt;
&lt;p&gt;Next, we open two accounts, one called &lt;code&gt;Assets:Commerzbank:EC&lt;&#x2F;code&gt;, which is an
&lt;code&gt;Asset&lt;&#x2F;code&gt; account with a physical bank (&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.commerzbank.de&#x2F;&quot;&gt;Commerzbank&lt;&#x2F;a&gt; in this example). This could
be, for example, where you get your salary. The second account is called
&lt;code&gt;Expenses:Supermarket&lt;&#x2F;code&gt; which is an &lt;code&gt;Expense&lt;&#x2F;code&gt; account. Such accounts track where
you&#x27;re spending your money. You can create more &lt;code&gt;Expense&lt;&#x2F;code&gt; accounts for things
like rent, car, fuel, etc.&lt;&#x2F;p&gt;
&lt;p&gt;Note that the &quot;accounts&quot; in Beancount don&#x27;t have to be real-world bank accounts.
They&#x27;re just a way for you to keep track of the flow of money. Apart from the
&lt;code&gt;Asset&lt;&#x2F;code&gt; and &lt;code&gt;Expense&lt;&#x2F;code&gt; accounts, there are a few other kinds of accounts
available which can be used for different purposes. For example, if you decide
to buy a house and take a loan, Beancount lets you define how much you still owe
the bank using a &lt;code&gt;Liability&lt;&#x2F;code&gt; account. The &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;furius.ca&#x2F;beancount&#x2F;doc&#x2F;index&quot;&gt;documentation&lt;&#x2F;a&gt; explains this in much
more detail.&lt;&#x2F;p&gt;
&lt;p&gt;Given something like this, all Beancount does is track where the money is
flowing. In this example there is just one transaction with roughly 30 euros
going from the Commerzbank account to the Supermarket account.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s exactly this counting that Beancount does extremely well. So well, that the
final result looks something like the following.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;beancount.github.io&#x2F;fava&#x2F;&quot;&gt;&lt;img src=&quot;https:&#x2F;&#x2F;sgoel.dev&#x2F;posts&#x2F;managing-personal-finances-using-python&#x2F;beancount-fava.png&quot; alt=&quot;Beancount Fava&quot; &#x2F;&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;flow&quot;&gt;Flow&lt;&#x2F;h2&gt;
&lt;p&gt;Here&#x27;s a very high level overview of what such a process looks like.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;At the beginning of every month, open your bank&#x27;s website and download all
your transactions for all your accounts for the previous month in the CSV
format. Almost every bank allows doing this, and if yours does not, then it&#x27;s
time to switch. I personally do this once a month, but the frequency depends
entirely on you. You could also do it weekly, or if you&#x27;re particularly
hardcore, then also daily.&lt;&#x2F;li&gt;
&lt;li&gt;Convert these CSV files to the &lt;code&gt;.beancount&lt;&#x2F;code&gt; format. This format is the DSL
that follows double entry bookkeeping and that Beancount understands.
Beancount provides you with a set of helpers to &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.google.com&#x2F;document&#x2F;d&#x2F;11EwQdujzEo2cxqaF5PgxCEZXWfKKQCYSMfdJowp_1S8&#x2F;edit&quot;&gt;import external data&lt;&#x2F;a&gt; so you
can do this conversion easily.&lt;&#x2F;li&gt;
&lt;li&gt;That&#x27;s basically it. Once your transactions are in the &lt;code&gt;.beancount&lt;&#x2F;code&gt; format,
you can ask Beancount for anything you&#x27;d like to know. You can either use the
console-based tools included along with Beancount, or you could install
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;beancount.github.io&#x2F;fava&#x2F;&quot;&gt;Fava&lt;&#x2F;a&gt; and look at all your financial history in colorful graphs.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;It might be some effort in the beginning to set things up, especially if you&#x27;re
looking to import historical data. You need to look at every transaction
individually and decide which account should this be posted to.&lt;&#x2F;p&gt;
&lt;p&gt;But that&#x27;s actually the point. This way you&#x27;re aware of what you&#x27;re doing with
your money. If you spend say €50 every day purchasing random stuff on Amazon,
all of that will come back and bite you at the end of the month when you have
to tag all those transactions manually.&lt;&#x2F;p&gt;
&lt;p&gt;There are some drawbacks to this setup. For instance, the website of the banks
where I have my accounts is not particularly optimized for downloading CSVs for
a given time range.&lt;&#x2F;p&gt;
&lt;p&gt;But I think with the level of flexibility and power this whole setup provides,
it&#x27;s a trade-off worth making. I highly doubt any other piece of personal
finance software comes even close.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Nim: First impressions</title>
        <published>2018-08-14T00:00:00+00:00</published>
        <updated>2018-08-14T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/nim-first-impressions/"/>
        <id>https://sgoel.dev/posts/nim-first-impressions/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/nim-first-impressions/">&lt;p&gt;As someone who writes software for a living, I think it&#x27;s a good idea to learn a
new language&#x2F;technology every once in a while. It makes you think differently
from what you&#x27;ve been focusing on in your normal 9-to-5.&lt;&#x2F;p&gt;
&lt;p&gt;In that spirit, I started playing around with &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;nim-lang.org&#x2F;&quot;&gt;Nim&lt;&#x2F;a&gt; last weekend. Nim is a neat
little language that has been around for a bit more than 10 years, but has been
(surprisingly) under the radar, at least if you compare it to how much publicity
Go and Rust generate.&lt;&#x2F;p&gt;
&lt;p&gt;The promise looks quite nice - high performance, (optional) garbage collection,
dependency-free binaries, compilation to JavaScript (!), and a clean syntax that
at times reminds me of Python.&lt;&#x2F;p&gt;
&lt;p&gt;After working through the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;nim-lang.org&#x2F;docs&#x2F;tut1.html&quot;&gt;official tutorial&lt;&#x2F;a&gt; and &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;nim-by-example.github.io&#x2F;getting_started&#x2F;&quot;&gt;Nim by Example&lt;&#x2F;a&gt; over the
weekend, I&#x27;ve come to really like the language. There are some quirks that I
wish weren&#x27;t there, but I&#x27;ll try to summarize my first impressions in the
following sections.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-compiler&quot;&gt;The Compiler&lt;&#x2F;h2&gt;
&lt;p&gt;If you&#x27;re used to writing code in an interpreted language, &quot;fighting the
compiler&quot; can be a thing.&lt;&#x2F;p&gt;
&lt;p&gt;But I&#x27;m pleased to report that the Nim compiler, while pedantic like all others,
is actually quite helpful. The error messages are (for the most part) concise
and tell you exactly what you should do to turn your code into a binary.
And it&#x27;s always nice to catch possible runtime errors at compile time.&lt;&#x2F;p&gt;
&lt;p&gt;For example, consider the following snippet that asks the user to enter a
number.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;nim&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; strutils&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;echo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;What&amp;#39;s your number? &amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;var&lt;&#x2F;span&gt;&lt;span&gt; number &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; strutils&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;parseInt&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;readLine&lt;&#x2F;span&gt;&lt;span&gt;(stdin))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;case&lt;&#x2F;span&gt;&lt;span&gt; number&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  of&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    echo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;One&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  of&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    echo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Two&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This fails to compile with the error message &lt;code&gt;not all cases are covered&lt;&#x2F;code&gt;, which
means that the compiler noticed that your &lt;code&gt;case&lt;&#x2F;code&gt; statement is not checking all
the possible cases and that this may cause unexpected behavior at runtime.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;kind-of-simple&quot;&gt;Kind-of simple&lt;&#x2F;h2&gt;
&lt;p&gt;Nim looks small and simple on the surface, but it&#x27;s a fairly advanced language.
There are features that let you do extremely funky things. You probably won&#x27;t
end up using those features in daily use (well, depending on what you&#x27;re using
it for), but you know that they are there.&lt;&#x2F;p&gt;
&lt;p&gt;I would argue that this is nice because the language feels approachable. It
feels like you can hold the entire language in your head (even though that may
not be the case). I don&#x27;t feel the urge to hide in a corner on seeing Nim code
as a beginner to the language. At the same time you know it in the back of your
head that when you need to do advanced things, you can.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;dependency-free-binaries&quot;&gt;Dependency-free binaries&lt;&#x2F;h2&gt;
&lt;p&gt;Coming from a Python world, this is &lt;strong&gt;huge&lt;&#x2F;strong&gt;. The &lt;strong&gt;simplest&lt;&#x2F;strong&gt; Python
deployments can still be fairly tricky to get right. There&#x27;s the whole dance of
&lt;code&gt;virtualenv&lt;&#x2F;code&gt;s, installing dependencies, n-number of decisions you need to take
before even starting. And god help you if you need to install something that&#x27;s
not pure Python.&lt;&#x2F;p&gt;
&lt;p&gt;Nothing is simpler than compiling the project and putting &lt;strong&gt;one binary&lt;&#x2F;strong&gt; on the
server and seeing things work.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;compile-time-constants&quot;&gt;Compile-time constants&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;nim&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span&gt; value &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; doSomethingExpensive&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;value&lt;&#x2F;code&gt; is set here at &lt;strong&gt;compile-time&lt;&#x2F;strong&gt;. Of course, if &lt;code&gt;doSomethingExpensive&lt;&#x2F;code&gt;
cannot be worked out at compile-time, the compiler will shout at you. But when
you&#x27;re looking to reduce every single bit of runtime overhead, this is extremely
valuable.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;productivity&quot;&gt;Productivity&lt;&#x2F;h2&gt;
&lt;p&gt;It was fascinating to observe that after spending effectively only a few hours
with the language, I felt like I was in a position to write &lt;strong&gt;working&lt;&#x2F;strong&gt; code.
I&#x27;ve had multiple moments writing Nim code when you write something and then
look at it and then go &quot;was that it?&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;The last time something like this happened was when I started learning Python.
It was astonishing that a programming language could put you in a position where
you can write &lt;strong&gt;working&lt;&#x2F;strong&gt; code so quickly.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;variable-assignment&quot;&gt;Variable assignment&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;nim&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;var&lt;&#x2F;span&gt;&lt;span&gt; x, y &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This sets the value of both &lt;code&gt;x&lt;&#x2F;code&gt; and &lt;code&gt;y&lt;&#x2F;code&gt; to be &lt;code&gt;3&lt;&#x2F;code&gt;, which seems slightly
confusing. Go does not compile something similar, complaining that there are 2
variables but 1 value.&lt;&#x2F;p&gt;
&lt;p&gt;To be honest, this is not a huge deal, but I can definitely see myself debugging
bugs caused by this behavior.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;methods&quot;&gt;Methods&lt;&#x2F;h2&gt;
&lt;p&gt;From &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;nim-lang.org&#x2F;docs&#x2F;tut2.html#object-oriented-programming-methods&quot;&gt;methods&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Adding a method to a class the programmer has no control over is impossible
or needs ugly workarounds.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;This is mentioned under the section &quot;disadvantages&quot;. I don&#x27;t think this is a
disadvantage. I would actually argue that this keeps the programmer from doing
funky things that harm readability.&lt;&#x2F;p&gt;
&lt;p&gt;I once spent 2 days trying to debug an issue in a Ruby on Rails codebase where a
third-party gem was monkey-patching some functions defined in &lt;code&gt;ActiveRecord&lt;&#x2F;code&gt;.
This was a classic example of modifying something you&#x27;re not supposed to modify.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;module-import-semantics&quot;&gt;Module import semantics&lt;&#x2F;h2&gt;
&lt;p&gt;So far this has been my biggest complaint with Nim.&lt;&#x2F;p&gt;
&lt;p&gt;Consider this simple program.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;nim&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; strutils&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; s &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Hello, World!&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;echo&lt;&#x2F;span&gt;&lt;span&gt; s&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;split&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;echo&lt;&#x2F;span&gt;&lt;span&gt; strutils&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;split&lt;&#x2F;span&gt;&lt;span&gt;(s)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;echo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; split&lt;&#x2F;span&gt;&lt;span&gt;(s)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The last three lines are all doing the same thing. So not only is the &lt;code&gt;split()&lt;&#x2F;code&gt;
function suddenly available on strings, but is also (optionally) a part of the
local namespace. It&#x27;s difficult to say if &lt;code&gt;split&lt;&#x2F;code&gt; is a part of &lt;code&gt;strutils&lt;&#x2F;code&gt; or a
built-in function or a function defined on &lt;code&gt;string&lt;&#x2F;code&gt; types (if you didn&#x27;t notice
the &lt;code&gt;import strutils&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
&lt;p&gt;After writing Python code for slightly more than 10 years, I have &quot;explicit is
better than implicit&quot; plastered all over my brain. So such behavior makes me a
little uneasy.&lt;&#x2F;p&gt;
&lt;p&gt;From what I understand, this is a consequence of &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Uniform_Function_Call_Syntax&quot;&gt;Uniform Function Call
Syntax&lt;&#x2F;a&gt;, but IMHO this feels like a step back in terms of being able to look at
a piece of code and immediately make sense out of it.&lt;&#x2F;p&gt;
&lt;p&gt;But the Nim developers know this complaint. There&#x27;s already a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;nim-lang&#x2F;Nim&#x2F;issues&#x2F;8013&quot;&gt;Github issue&lt;&#x2F;a&gt;,
and the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;forum.nim-lang.org&#x2F;t&#x2F;3783&quot;&gt;forum thread&lt;&#x2F;a&gt; even has a proposal that might be implemented in one of
the next releases. So I&#x27;m hopeful!&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;Overall, I like Nim. If it&#x27;s not painfully obvious, this article barely
scratched the surface. There is so much more to the language that I haven&#x27;t
written about here for the sake of keeping this article short.&lt;&#x2F;p&gt;
&lt;p&gt;There are some design-things which I either don&#x27;t get or don&#x27;t agree with. But
on the other hand it provides many nice features without necessarily
compromising on speed or introducing complexity. My first attempt at picking up
one of the new systems-programming languages was at Rust, but it introduced so
much complexity that it was overwhelming.&lt;&#x2F;p&gt;
&lt;p&gt;Nim feels approachable, and gives you the feeling that it&#x27;s powerful for when
you need something more than just simple.&lt;&#x2F;p&gt;
&lt;p&gt;Given that I&#x27;ve spent less than a week working with it and still like it so
much, I can definitely see myself writing more Nim code in the future.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Switching from KeePassXC to Bitwarden</title>
        <published>2018-08-07T00:00:00+00:00</published>
        <updated>2018-08-07T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/switching-from-keepassxc-to-bitwarden/"/>
        <id>https://sgoel.dev/posts/switching-from-keepassxc-to-bitwarden/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/switching-from-keepassxc-to-bitwarden/">&lt;p&gt;I have been looking to replace my password management setup for a few months
now. Up until now I was using &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;keepassxc.org&#x2F;&quot;&gt;KeePassXC&lt;&#x2F;a&gt;, but I never got around to putting in
the work to make it work properly on mobile.&lt;&#x2F;p&gt;
&lt;p&gt;I understand that the standard solution is to just get the file synced on your
phone using Dropbox and then install a mobile app which can read the file off of
Dropbox. But the idea that all my passwords are in one single file somewhere on
the internet (cloud, if you will) is a bit disturbing.&lt;&#x2F;p&gt;
&lt;p&gt;Incidentally, this is also the reason I never got around to using &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;1password.com&#x2F;&quot;&gt;1password&lt;&#x2F;a&gt;,
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.lastpass.com&#x2F;&quot;&gt;Lastpass&lt;&#x2F;a&gt;, or other cloud-hosted password managers.&lt;&#x2F;p&gt;
&lt;p&gt;This is not to discount the work that the security folks at these companies are
putting in on a daily basis to make their services secure. And I know that my
passwords file is encrypted. I&#x27;ve just seen enough mistakes happen and I&#x27;d like
to avoid being a part of one.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;requirements&quot;&gt;Requirements&lt;&#x2F;h2&gt;
&lt;p&gt;My requirements were fairly simple. I wanted something that&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;is open-source&lt;&#x2F;li&gt;
&lt;li&gt;allows self-hosting&lt;&#x2F;li&gt;
&lt;li&gt;works on mobile&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;... in that order.&lt;&#x2F;p&gt;
&lt;p&gt;Turns out that just these 3 requirements narrowed down the search to
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;bitwarden.com&#x2F;&quot;&gt;Bitwarden&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;bitwarden&quot;&gt;Bitwarden&lt;&#x2F;h2&gt;
&lt;p&gt;Bitwarden is &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bitwarden&quot;&gt;open source&lt;&#x2F;a&gt;. There&#x27;s a &lt;code&gt;core&lt;&#x2F;code&gt; server written in C# and then there
are multiple client apps (iOS, Android, desktop).&lt;&#x2F;p&gt;
&lt;p&gt;The backend appears quite heavy weight. Being written in C# and talking to a SQL
server installation, it&#x27;s not exactly what I would call &quot;deployment friendly&quot;.
Luckily, they provide a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bitwarden&#x2F;core#deploy&quot;&gt;Docker image&lt;&#x2F;a&gt; which you can use to self-host the whole
thing.&lt;&#x2F;p&gt;
&lt;p&gt;While this works on a modern machine, I wanted to run Bitwarden on a spare
Raspberry Pi connected to my home network where the system requirements are a
bit less than what the Docker image requires.&lt;&#x2F;p&gt;
&lt;p&gt;It turns out that I&#x27;m not the first person to run into this problem. &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;jcs.org&quot;&gt;@jcs&lt;&#x2F;a&gt;
already &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;jcs.org&#x2F;2017&#x2F;11&#x2F;17&#x2F;bitwarden&quot;&gt;wrote&lt;&#x2F;a&gt; a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;jcs&#x2F;rubywarden&quot;&gt;Ruby server&lt;&#x2F;a&gt; which is API-compatible with the &quot;official&quot;
Bitwarden backend. This implementation is &lt;strong&gt;very&lt;&#x2F;strong&gt; lightweight, and completely
doable for the Pi. So all I had to do was create a new &lt;code&gt;bitwarden&lt;&#x2F;code&gt; user on the
Pi, install &lt;code&gt;rvm&lt;&#x2F;code&gt;, &lt;code&gt;git clone&lt;&#x2F;code&gt; the repository, and then start the server
process.&lt;&#x2F;p&gt;
&lt;p&gt;The only problem left now was setting up a static IP for the Pi so that the
desktop client on my laptop and the iOS app on my phone know the
(static &amp;amp; private) IP address they should be connecting to. Luckily my modem
supported allocating the same IP to devices based on their MAC addresses so this
was also easy.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;migrating-from-keepassxc&quot;&gt;Migrating from KeePassXC&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;jcs&#x2F;rubywarden&quot;&gt;rubywarden&lt;&#x2F;a&gt; includes a script to import existing KeePassXC database files, and
it worked without any problems. The script somehow didn&#x27;t handle my KeePassXC
folders very well. So an entry called &quot;Google&quot; in the &quot;Internet&quot; folder was
imported as &quot;Internet&#x2F;Google&quot;. Those slashes look pretty annoying, so I took
some time out to delete some unused passwords and organize the rest into
folders.&lt;&#x2F;p&gt;
&lt;p&gt;Migration was a non-issue, I would say.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;caveats&quot;&gt;Caveats&lt;&#x2F;h2&gt;
&lt;p&gt;The only caveat with this system that I can think of right now is that when
you&#x27;re not at home, you won&#x27;t be able to save&#x2F;edit&#x2F;delete passwords. You&#x27;ll be
able to read just fine, but editing won&#x27;t work.&lt;&#x2F;p&gt;
&lt;p&gt;The reason is that your client apps are configured to talk to a private IP
address (of the Pi in this case). But so far I haven&#x27;t had the need to
save&#x2F;edit&#x2F;delete passwords when I&#x27;m outside. And reading them works just fine.&lt;&#x2F;p&gt;
&lt;p&gt;If this is important for you, then this is probably the price you pay for
keeping the passwords on a machine you can physically look at.&lt;&#x2F;p&gt;
&lt;p&gt;But overall, I find this setup quite nice to work with.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Imprint</title>
        <published>2018-08-01T00:00:00+00:00</published>
        <updated>2018-08-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/imprint/"/>
        <id>https://sgoel.dev/imprint/</id>
        
        <content type="html" xml:base="https://sgoel.dev/imprint/">&lt;h2 id=&quot;legal-disclosure&quot;&gt;Legal Disclosure&lt;&#x2F;h2&gt;
&lt;p&gt;Information in accordance with Section 5 TMG&lt;&#x2F;p&gt;
&lt;h2 id=&quot;contact-information&quot;&gt;Contact Information&lt;&#x2F;h2&gt;
&lt;p&gt;Siddhant Goel&lt;&#x2F;p&gt;
&lt;p&gt;E-Mail: &lt;a href=&quot;mailto:me@sgoel.dev&quot;&gt;me@sgoel.dev&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Internet Address: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;sgoel.dev&quot;&gt;https:&#x2F;&#x2F;sgoel.dev&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;disclaimer&quot;&gt;Disclaimer&lt;&#x2F;h2&gt;
&lt;h2 id=&quot;accountability-for-content&quot;&gt;Accountability for content&lt;&#x2F;h2&gt;
&lt;p&gt;The contents of our pages have been created with the utmost care. However, we
cannot guarantee the contents&#x27; accuracy, completeness or topicality. According
to statutory provisions, we are furthermore responsible for our own content on
these web pages. In this matter, please note that we are not obliged to monitor
the transmitted or saved information of third parties, or investigate
circumstances pointing to illegal activity. Our obligations to remove or block
the use of information under generally applicable laws remain unaffected by this
as per §§ 8 to 10 of the Telemedia Act (TMG).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;accountability-for-links&quot;&gt;Accountability for links&lt;&#x2F;h2&gt;
&lt;p&gt;Responsibility for the content of external links (to web pages of third parties)
lies solely with the operators of the linked pages. No violations were evident
to us at the time of linking. Should any legal infringement become known to us,
we will remove the respective link immediately.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;copyright&quot;&gt;Copyright&lt;&#x2F;h2&gt;
&lt;p&gt;Our web pages and their contents are subject to German copyright law. Unless
expressly permitted by law, every form of utilizing, reproducing or processing
works subject to copyright protection on our web pages requires the prior
consent of the respective owner of the rights. Individual reproductions of a
work are only allowed for private use. The materials from these pages are
copyrighted and any unauthorized use may violate copyright laws.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Setting up flake8 for Cython</title>
        <published>2018-07-03T00:00:00+00:00</published>
        <updated>2018-07-03T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/setting-up-flake8-for-cython/"/>
        <id>https://sgoel.dev/posts/setting-up-flake8-for-cython/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/setting-up-flake8-for-cython/">&lt;p&gt;I like enabling &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;flake8.pycqa.org&#x2F;en&#x2F;latest&#x2F;&quot;&gt;flake8&lt;&#x2F;a&gt; on my Python projects. It helps prevent a lot of
&quot;does this code look pretty&quot; arguments with contributors. These arguments feel
like bike-shedding because they are very subjective and almost everyone has a
different idea of what &quot;pretty&quot; means.&lt;&#x2F;p&gt;
&lt;p&gt;Disagreeing on such matters often feels petty. At the end of the day, you want
your codebase to look consistent. Multiple definitions of &quot;pretty&quot; in one
codebase does not help with that.&lt;&#x2F;p&gt;
&lt;p&gt;Standardizing on a set of rules and letting a script enforce them is nice. If
you hook it up to your CI server, it&#x27;s easy to end such discussions with a &quot;hey
it&#x27;s not me, it&#x27;s the robots that are complaining&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;This morning I enabled &lt;code&gt;flake8&lt;&#x2F;code&gt; on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;streaming-form-data&quot;&gt;streaming-form-data&lt;&#x2F;a&gt;. Now, most of the code
in this repository is written in &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;cython.org&quot;&gt;Cython&lt;&#x2F;a&gt;, which is not strictly Python, but a
strange mix of C and Python. And since it&#x27;s a mix with some Python in there, I
would still like to have &lt;code&gt;flake8&lt;&#x2F;code&gt; do its thing so that the codebase looks
consistent.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;making-files-visible&quot;&gt;Making files visible&lt;&#x2F;h2&gt;
&lt;p&gt;Cython files have the &lt;code&gt;.pyx&lt;&#x2F;code&gt; extension, which &lt;code&gt;flake8&lt;&#x2F;code&gt; seems to ignore by
default. This can be easily fixed by adding a &lt;code&gt;tox.ini&lt;&#x2F;code&gt; file in your project
directory with the following contents.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;[flake8]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;filename&lt;&#x2F;span&gt;&lt;span&gt; = *.py, *.pyx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This instructs &lt;code&gt;flake8&lt;&#x2F;code&gt; to include &lt;code&gt;.pyx&lt;&#x2F;code&gt; files in addition to the default &lt;code&gt;.py&lt;&#x2F;code&gt;
ones in the list of files it&#x27;s going to check.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;avoiding-false-positives&quot;&gt;Avoiding false positives&lt;&#x2F;h2&gt;
&lt;p&gt;Since Cython has a slightly different syntax than Python, it gets confused about
reporting violations on perfectly normal Cython code.&lt;&#x2F;p&gt;
&lt;p&gt;As an example, let&#x27;s look at error &lt;code&gt;E225&lt;&#x2F;code&gt;. &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pep8.readthedocs.io&#x2F;en&#x2F;release-1.7.x&#x2F;intro.html#error-codes&quot;&gt;PEP8 defines&lt;&#x2F;a&gt; this error code to be
raised on &quot;missing whitespace around operator&quot;. So something like &lt;code&gt;a = b * c&lt;&#x2F;code&gt;
would fail this check, since PEP8 would like to see some spaces between &lt;code&gt;*&lt;&#x2F;code&gt; and
&lt;code&gt;c&lt;&#x2F;code&gt; (or &lt;code&gt;b&lt;&#x2F;code&gt; and &lt;code&gt;*&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
&lt;p&gt;But the situation is slightly different in Cython, because now a &lt;code&gt;*&lt;&#x2F;code&gt; could refer
to pointers. A statement like &lt;code&gt;cdef const char *ptr&lt;&#x2F;code&gt; is declaring a pointer
variable. But &lt;code&gt;flake8&lt;&#x2F;code&gt; does not know that.&lt;&#x2F;p&gt;
&lt;p&gt;What I&#x27;d like to do here is to tell &lt;code&gt;flake8&lt;&#x2F;code&gt; to ignore certain warnings for
specific files. Unfortunately, &lt;code&gt;flake8&lt;&#x2F;code&gt; does not have native support for this
(yet). But what it does have is support for plugins. And luckily, someone wrote
a plugin for exactly this situation.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;snoack&#x2F;flake8-per-file-ignores&quot;&gt;flake8-per-file-ignores&lt;&#x2F;a&gt; lets you associate filenames with error codes you&#x27;d
like to ignore in those files. This can be done by adding a &lt;code&gt;per-file-ignores&lt;&#x2F;code&gt;
setting to your &lt;code&gt;tox.ini&lt;&#x2F;code&gt;, so it looks something like the following.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;[flake8]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;filename&lt;&#x2F;span&gt;&lt;span&gt; = *.py, *.pyx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;per-file-ignores&lt;&#x2F;span&gt;&lt;span&gt; =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    streaming_form_data&#x2F;_parser.pyx: E225, E226&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This instructs &lt;code&gt;flake8&lt;&#x2F;code&gt; to not report errors  &lt;code&gt;E225&lt;&#x2F;code&gt; and &lt;code&gt;E226&lt;&#x2F;code&gt; in
&lt;code&gt;streaming_form_data&#x2F;_parser.pyx&lt;&#x2F;code&gt;. For all other files, &lt;code&gt;flake8&lt;&#x2F;code&gt; defaults to the
standard rules.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Sharing files with family in 2018</title>
        <published>2018-07-01T00:00:00+00:00</published>
        <updated>2018-07-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/sharing-files-with-family-in-2018/"/>
        <id>https://sgoel.dev/posts/sharing-files-with-family-in-2018/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/sharing-files-with-family-in-2018/">&lt;p&gt;My wife and I often go hiking, and one of these evenings we decided to hike up a
mountain, and set up a tent on top so we can stay overnight and see the sunrise
from up there.&lt;&#x2F;p&gt;
&lt;p&gt;I took lots of photos which I would like to send her. I have an iPhone and she&#x27;s
using an Android phone. The question is - how to make that happen?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;let-s-just-send-them-over-whatsapp&quot;&gt;Let&#x27;s just send them over WhatsApp.&lt;&#x2F;h2&gt;
&lt;p&gt;Unless you&#x27;ve been living under a rock, you&#x27;re probably aware of the huge
privacy screw-up that is called Facebook. WhatsApp is owned by Facebook, so it&#x27;s
safe to assume that whatever stuff you write or whatever files you share via
WhatsApp &lt;strong&gt;will&lt;&#x2F;strong&gt; at some point end up on Facebook servers and &lt;strong&gt;will&lt;&#x2F;strong&gt; be sold
to third parties.&lt;&#x2F;p&gt;
&lt;p&gt;I don&#x27;t want that to happen. So we&#x27;re not pursuing this option any further.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;alright-how-about-signal&quot;&gt;Alright, how about Signal?&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;signal.org&#x2F;&quot;&gt;Signal&lt;&#x2F;a&gt; is better. They have a photo of Edward Snowden on their homepage saying
&quot;Use anything by Open Whisper Systems&quot;. For me that&#x27;s a good enough sign that I
can use this app.&lt;&#x2F;p&gt;
&lt;p&gt;Convinced, both of us install Signal on our respective phones. Next, I open the
Photos app on my iPhone, select the 68 photos and try to share them via Signal.
Strangely, it only shows me 1 photo that it&#x27;ll send.&lt;&#x2F;p&gt;
&lt;p&gt;Did I just find a bug?&lt;&#x2F;p&gt;
&lt;p&gt;Interestingly, no. This is a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;signalapp&#x2F;Signal-Android&#x2F;issues&#x2F;2159&quot;&gt;feature request&lt;&#x2F;a&gt; since 2014. Looks like the devs
haven&#x27;t had the time to merge this feature in. My sense of self-entitlement
(which comes from installing and using an app that didn&#x27;t cost me a cent but
costed some other people a &lt;strong&gt;lot&lt;&#x2F;strong&gt; of their time and effort) is screaming but
I shut it up and start thinking about other alternatives.&lt;&#x2F;p&gt;
&lt;p&gt;(Btw, huge shout out to the people at Signal for making this messenger happen!)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;wait-isn-t-icloud-built-right-in&quot;&gt;Wait, isn&#x27;t iCloud built right in?&lt;&#x2F;h2&gt;
&lt;p&gt;Right. iPhones have iCloud. I could just sync those photos up to iCloud and have
my wife use iCloud on her phone so she has the photos as well. This should work,
right?&lt;&#x2F;p&gt;
&lt;p&gt;Not really. She&#x27;s on an Android phone, and the top 10 results for &quot;icloud
android&quot; on DuckDuckGo are blog posts. And when the top few search results for
something that you&#x27;re searching for are blog posts or &quot;howto&quot; articles, that&#x27;s a
sign that the solution is not straight forward.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;this-is-a-file-sharing-problem-not-photo-sharing-google-drive&quot;&gt;This is a file-sharing problem, not photo-sharing. Google Drive?&lt;&#x2F;h2&gt;
&lt;p&gt;Finally my developer mind starts generalizing the problem. Why am I thinking of
these files in terms of photos? These are all just files. Any solution that
lets me sync files between different phones should work.&lt;&#x2F;p&gt;
&lt;p&gt;I don&#x27;t want to use Google Drive for this purpose. Granted, it&#x27;s a pretty
attractive offering and the free storagae space they&#x27;re giving out is pretty
generous and Picasa probably auto-categorizes all your photos and lets you apply
fancy filters and what not.&lt;&#x2F;p&gt;
&lt;p&gt;But I think Google is getting too big, and not that them not having my useless
files would make a difference to their growth, but I would still like to avoid
using Drive to store my personal data. To some extent I do believe that &quot;if
you&#x27;re not paying, you are the product&quot;, and it seems to apply here.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;dropbox&quot;&gt;Dropbox&lt;&#x2F;h2&gt;
&lt;p&gt;Dropbox should work. I use it on a daily basis at work and it works good enough.
Also, I pay them 10 euros a month so I have reasons to believe that I&#x27;m not the
product.&lt;&#x2F;p&gt;
&lt;p&gt;Seems promising so far. Why didn&#x27;t I start with Dropbox in the first place?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;mobile-app&quot;&gt;Mobile App&lt;&#x2F;h2&gt;
&lt;p&gt;Since the photos are on my phone, I download and install the Dropbox iOS app.
Now, I have a weird way of thinking in that I don&#x27;t allow apps full access to
all my photos. If an app supports doing something with photos (Instagram for
example), I disable the app&#x27;s access to my photos, and instead open the photo
in the Photos app and then share using iOS sharing.&lt;&#x2F;p&gt;
&lt;p&gt;You can call me crazy, and you probably would be right, but I just don&#x27;t
feel comfortable doing it any other way. Some day when iOS lets me give out
permissions on select folders to select apps - maybe. Until then, that&#x27;s not
happening.&lt;&#x2F;p&gt;
&lt;p&gt;So then I open the Photos app again, select one photo, and share it with the
Dropbox app to test things out. It works! I can see the photo appear on my
wife&#x27;s phone automagically.&lt;&#x2F;p&gt;
&lt;p&gt;Next, I select the remaining 67 photos and click on the share icon. Strangely, I
don&#x27;t see the Dropbox icon anymore. Does sharing multiple files not work? I have
no idea. At this point, I&#x27;m starting to lose my patience, so I decide not to
investigate this option any further and look at another alternative.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;connect-iphone-to-laptop&quot;&gt;Connect iPhone to laptop&lt;&#x2F;h2&gt;
&lt;p&gt;Maybe if I connect my phone to my laptop, I could copy the files directly to the
Dropbox folder.&lt;&#x2F;p&gt;
&lt;p&gt;I use Linux (GNOME) on my personal laptop. Initially I thought this might be a
problem, but (surprisingly) there weren&#x27;t really any issues here.&lt;&#x2F;p&gt;
&lt;p&gt;I plugged in my iPhone to the laptop. The phone asked me whether or not to trust
this computer (I clicked &quot;yes&quot;). GNOME immediately identified the phone as an
external storage device, and I could look inside the DCIM folder which contains
all the media on the phone. The folder structure does look a bit funky; there
are folders called 100APPLE and 101APPLE and things like that, but I&#x27;m OK with
that. All good as long as I can see the stupid photos.&lt;&#x2F;p&gt;
&lt;p&gt;The &quot;Files&quot; application on GNOME, for some reason, decides not to show me the
thumbnails. Which means I had to do a binary-search-kind-of-a-thing on the
filenames to figure out where the photoset starts and where it ends.&lt;&#x2F;p&gt;
&lt;p&gt;Minor annoyance, and it took a few minutes extra to find the first&#x2F;last photos
by their names, but I guess that&#x27;s the price you pay for being a bit crazy.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;So what is all this supposed to mean?&lt;&#x2F;p&gt;
&lt;p&gt;Even though the services mentioned in this blog post do a fine job at serving
99% of the use cases they&#x27;re targeting, the entire process can be quite
frustrating if what you&#x27;re trying to do falls under the remaining 1%.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Lessons learned working from home</title>
        <published>2018-06-06T00:00:00+00:00</published>
        <updated>2018-06-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/lessons-learned-working-from-home/"/>
        <id>https://sgoel.dev/posts/lessons-learned-working-from-home/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/lessons-learned-working-from-home/">&lt;p&gt;It&#x27;s been close to 2 years now that I&#x27;ve been working from home. Home-office is
something that&#x27;s viewed differently in tech circles. Some people like it, some
don&#x27;t.&lt;&#x2F;p&gt;
&lt;p&gt;I for one, absolutely love it. Since starting home office, I&#x27;ve felt more
productive at work, I&#x27;ve started a bunch of &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;streaming-form-data&quot;&gt;open&lt;&#x2F;a&gt; &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;beancount-dkb&quot;&gt;source&lt;&#x2F;a&gt; &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;tornado-sqlalchemy&quot;&gt;projects&lt;&#x2F;a&gt;
(which never in my entire life I thought I would have the time and energy for),
and I&#x27;ve saved so much time not commuting, it sometimes feels like cheating. All
in all, highly recommended.&lt;&#x2F;p&gt;
&lt;p&gt;So now that we know that this post is absolutely not biased, here&#x27;s my take on
home office.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;it-s-not-for-everyone&quot;&gt;It&#x27;s not for everyone.&lt;&#x2F;h2&gt;
&lt;p&gt;It really is not. Some people prefer having other people around, some don&#x27;t. I&#x27;m
somewhere in the middle. I do like having people around me, especially (but not
limited to) the ones who think like I do (like software developers), but not
having people around is not a huge deal breaker for me. I also prefer the peace
of my work-room in my apartment over open offices. The ease with which people
can interrupt others in open offices is something I find extremely counter
productive, especially in professions which require you to focus for longer
periods of time.  But bitching about open offices is for a different post, I
guess.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;work-room-is-strictly-for-work-and-maybe-your-dog&quot;&gt;Work room is strictly for work. And maybe your dog.&lt;&#x2F;h2&gt;
&lt;p&gt;This one is important. Pick one room in your apartment, and call it your work
room. In that room, only work gets done. Nothing else. No beds, no couch, no TV,
nothing that can distract you in any form. This really requires self-discipline
before you get the hang of it. It&#x27;s quite easy and tempting to switch on the TV
and watch something when it&#x27;s right there in front of you. When working from
home, you need to eliminate such triggers.&lt;&#x2F;p&gt;
&lt;p&gt;This also needs being upfront with your family members. Close the doors while
you&#x27;re &quot;at work&quot;, which means they can&#x27;t interrupt you unless it&#x27;s something
very very urgent and just cannot wait. In all other cases, they need to wait for
you to take the next break.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;overcommunication-is-better-than-less-communication&quot;&gt;Overcommunication is better than less communication.&lt;&#x2F;h2&gt;
&lt;p&gt;Many a times I&#x27;ve noticed that a phone call is better than a long Slack chat.
Over text, you could be misinterpreted, you might come across as rude when you
were not, it might take you a long time to explain something. There are plenty
of things. I have a completely unscientific (but probable) belief that humans
have not evolved to be texting each other. Instead, we have evolved to be
talking to each other. So make use of the work that evolution has put in you
over millions of years, and &lt;strong&gt;talk&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&quot;Out of sight out of mind&quot; has some level of truth to it. Some face time is
needed. If you&#x27;re not seeing your colleagues on a daily basis, scheduling
&lt;strong&gt;some&lt;&#x2F;strong&gt; time where you physically get to be in front of each other is
also important.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;some-social-interaction-is-required&quot;&gt;Some social interaction is required.&lt;&#x2F;h2&gt;
&lt;p&gt;Being at home all day can take its toll. It doesn&#x27;t make a difference how
introverted you are, or how anti-social you think you are, some social
interaction is required to keep you sane. I don&#x27;t have a solid reasoning for
this; I&#x27;m only speaking from experience. I&#x27;m an introvert, and have some level
of social anxiety. I still feel that every once in a while, I need to meet some
friends, or go to a meetup and meet new people.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;be-transparent-to-your-colleagues&quot;&gt;Be transparent to your colleagues.&lt;&#x2F;h2&gt;
&lt;p&gt;Working remotely requires putting a lot of trust in your colleagues. Everyone
trusts each other to get work done, and being at home does not mean you&#x27;re
allowed to slack off. Home office does introduce some level of opacity into what
the other person is doing, but you should be overcommunicating to make up for
that.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;don-t-work-in-your-pajamas&quot;&gt;Don&#x27;t work in your pajamas.&lt;&#x2F;h2&gt;
&lt;p&gt;When you&#x27;re working from home, it&#x27;s tempting to wake up and start working
without freshening up. Or work in your pajamas or those torn shorts you&#x27;re not
wearing outside because they&#x27;re torn.&lt;&#x2F;p&gt;
&lt;p&gt;Don&#x27;t do that. By doing that, you&#x27;re sending a signal to your brain that you&#x27;re
not &lt;strong&gt;really&lt;&#x2F;strong&gt; at work, so your brain responds in kind. Dress up just like you
would if you were to go to an office. You don&#x27;t have to put on a suit, but at
least something nice and comfortable.&lt;&#x2F;p&gt;
&lt;p&gt;Rule of thumb: if you would be embarassed of wearing something outside, don&#x27;t
wear it to &quot;work&quot;.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;All in all, home-office, depending on what kind of a person you are, can be a
huge perk. It allows you to cut down on time wasted while commuting, lets you
set your own working hours, lets you setup your work environment &lt;strong&gt;exactly&lt;&#x2F;strong&gt;
how you like it, besides letting you do plenty of other things. Of course, there
can be downsides, but I think they&#x27;re more of those &quot;terms and conditions apply&quot;
asterisks rather than &quot;downsides&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;YMMV. 🙃&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Raise better Exceptions</title>
        <published>2018-04-16T00:00:00+00:00</published>
        <updated>2018-04-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/raise-better-exceptions/"/>
        <id>https://sgoel.dev/posts/raise-better-exceptions/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/raise-better-exceptions/">&lt;p&gt;We use Python extensively at work, and our web application talks to a PostgreSQL
database using the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;docs.sqlalchemy.org&#x2F;en&#x2F;latest&#x2F;&quot;&gt;SQLAlchemy&lt;&#x2F;a&gt; database toolkit.&lt;&#x2F;p&gt;
&lt;p&gt;One evening last week, I received an alert from &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;sentry.io&quot;&gt;Sentry&lt;&#x2F;a&gt; about some database
connections timing out.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;DatabaseError: (psycopg2.DatabaseError) SSL SYSCALL error: Connection timed out&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; [SQL: &amp;#39;SELECT pg_advisory_lock(%(key)s)&amp;#39;] [parameters: {&amp;#39;key&amp;#39;: 68197969}]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; (Background on this error at: http:&#x2F;&#x2F;sqlalche.me&#x2F;e&#x2F;4xp6)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The first two lines of the exception tells you what happened. But the
interesting bit is the next line.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; (Background on this error at: http:&#x2F;&#x2F;sqlalche.me&#x2F;e&#x2F;4xp6)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Not only did the library tell me what happened. But it also told me what the
background on this error is, and what a possible fix could look like.&lt;&#x2F;p&gt;
&lt;p&gt;This is &lt;strong&gt;very&lt;&#x2F;strong&gt; cool.&lt;&#x2F;p&gt;
&lt;p&gt;As a developer, my first instinct when I run into an exception which I&#x27;m
clueless about, is to basically copy paste the error into Google and see what
comes up. If you&#x27;re lucky, you&#x27;ll probably find a StackOverflow thread that
talks about exactly the same problem. And if you&#x27;re even luckier, that thread
might even have an accepted solution.&lt;&#x2F;p&gt;
&lt;p&gt;Often times though, you&#x27;re not so lucky, and the situation is best described by
the following XKCD.&lt;&#x2F;p&gt;
&lt;div class=&quot;text-center&quot;&gt;
    &lt;a href=&quot;https:&#x2F;&#x2F;xkcd.com&#x2F;979&#x2F;&quot;&gt;
        &lt;img src=&quot;https:&#x2F;&#x2F;imgs.xkcd.com&#x2F;comics&#x2F;wisdom_of_the_ancients.png&quot;
             alt=&quot;Wisdom of the ancients&quot;&gt;
    &lt;&#x2F;a&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;In such a situation, the only other alterantive is to read the source code and
try to figure things out. Depending on the code quality, this may or may not be
a pleasant experience, but that&#x27;s for another blog post.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re writing a library that&#x27;s being used by other people, take a moment to
think about the exceptions that it can raise, the different reasons why those
exceptions come into picture, and what a possible fix could be. Or if you don&#x27;t
want to go to those lengths, then at least raise relevant exceptions. Don&#x27;t be
the lazy dev who writes &lt;code&gt;raise Exception()&lt;&#x2F;code&gt;. Replace it with something more
specific like &lt;code&gt;raise ValueError(&#x27;Expected integers&#x27;)&lt;&#x2F;code&gt;. Your users would thank
you.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Beancount DKB</title>
        <published>2018-04-11T00:00:00+00:00</published>
        <updated>2018-04-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/beancount-dkb/"/>
        <id>https://sgoel.dev/posts/beancount-dkb/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/beancount-dkb/">&lt;p&gt;Last week I open-sourced &lt;code&gt;beancount-dkb&lt;&#x2F;code&gt;, which is a Python package that
provides importer classes for converting CSV exports of &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.dkb.de&quot;&gt;DKB&lt;&#x2F;a&gt; (Deutsche Kredit
Bank) account summaries into the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;bitbucket.org&#x2F;blais&#x2F;beancount&#x2F;overview&quot;&gt;Beancount&lt;&#x2F;a&gt; format.&lt;&#x2F;p&gt;
&lt;p&gt;For the past few months, I&#x27;ve been looking into keeping tabs on my personal
expenses. There are &lt;strong&gt;plenty&lt;&#x2F;strong&gt; of tools that let you do that. The problem is, I
don&#x27;t like the idea of a single company having access to all my bank accounts. I
honestly don&#x27;t care what the privacy policy says or what&#x27;s in the security
whitepaper.  I&#x27;ve worked at enough software companies and talked to enough
software engineers to know how things really work.&lt;&#x2F;p&gt;
&lt;p&gt;While searching, I stumbled upon the concept of &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;plaintextaccounting.org&#x2F;&quot;&gt;Plain Text Accounting&lt;&#x2F;a&gt;. The
concept is simple - you maintain a list of all your transactions in a plain text
file, which &lt;strong&gt;you&lt;&#x2F;strong&gt; are responsible for curating (hence no external access).
This list of transactions follows the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Double-entry_bookkeeping_system&quot;&gt;Double Entry Accounting&lt;&#x2F;a&gt; method, and is
written in a domain specific language (which may vary slightly from tool to
tool).&lt;&#x2F;p&gt;
&lt;p&gt;This workflow is quite appealing. You download your account statements&#x2F;summaries
from your bank&#x27;s website, run them through a script to convert them to a format
that your tool of choice works with, and then run reporting on top. There are
multiple such tools available to adopt this flow, the most prominent ones being
&lt;code&gt;ledger&lt;&#x2F;code&gt;, &lt;code&gt;hledger&lt;&#x2F;code&gt;, and &lt;code&gt;beancount&lt;&#x2F;code&gt;. I settled on using &lt;code&gt;beancount&lt;&#x2F;code&gt; simply
because I like Python.&lt;&#x2F;p&gt;
&lt;p&gt;The only problem was that my account is with &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.dkb.de&quot;&gt;DKB&lt;&#x2F;a&gt;, and there were no existing
conversion helpers to convert the CSV that I get from DKB to a format that
&lt;code&gt;beancount&lt;&#x2F;code&gt; can work with.&lt;&#x2F;p&gt;
&lt;p&gt;Luckily, &lt;code&gt;beancount&lt;&#x2F;code&gt; defines how to ingest external transaction data. The idea
is to implement a class that sticks to the &lt;code&gt;beancount.ingest.importer.Importer&lt;&#x2F;code&gt;
interface provided by &lt;code&gt;beancount&lt;&#x2F;code&gt;. The main job of this class is to take the raw
transactions data from your bank and output data in the beancount format.&lt;&#x2F;p&gt;
&lt;p&gt;This ingestion is extremely &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.google.com&#x2F;document&#x2F;d&#x2F;11EwQdujzEo2cxqaF5PgxCEZXWfKKQCYSMfdJowp_1S8&#x2F;edit#heading=h.z153vh2ll6ix&quot;&gt;well documented&lt;&#x2F;a&gt;. So I spent a weekend writing two
such importer classes - &lt;code&gt;ECImporter&lt;&#x2F;code&gt; (which works with EC account summaries),
and &lt;code&gt;CreditImporter&lt;&#x2F;code&gt; (which works on Credit Card account summaries). I&#x27;ve used
it to import my own transaction data starting from 2015 onwards, and the entire
setup works extremely well.&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, the module is on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pypi.org&#x2F;project&#x2F;beancount-dkb&#x2F;&quot;&gt;PyPI&lt;&#x2F;a&gt; under the name &lt;code&gt;beancount-dkb&lt;&#x2F;code&gt;, and the source
is on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;beancount-dkb&quot;&gt;Github&lt;&#x2F;a&gt;. Hope it&#x27;s useful!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Always be Questioning</title>
        <published>2018-01-26T00:00:00+00:00</published>
        <updated>2018-01-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/always-be-questioning/"/>
        <id>https://sgoel.dev/posts/always-be-questioning/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/always-be-questioning/">&lt;blockquote&gt;
&lt;p&gt;People don&#x27;t want to buy a quarter-inch drill. They want a quarter-inch hole!&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;A few years back I worked at a relatively small startup, with about 6-8 people
on the engineering team. Smart coworkers, extremely high level of code quality,
everyone eager to help each other out, basically everything an engineer could
ask for.&lt;&#x2F;p&gt;
&lt;p&gt;But the most important lesson I learned from working at this place was &quot;always
be questioning&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;My CTO at that time, was not just the smartest person I&#x27;ve ever met (with an
absurdly high margin), but was also one of the most pragmatic engineers I&#x27;ve had
the chance to work with. For every single decision we had to make as a team,
which involved putting a sizeable chunk of our engineering resources to use, we
had to handle the following three questions -&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Do we need to do this &lt;strong&gt;at all&lt;&#x2F;strong&gt;?&lt;&#x2F;li&gt;
&lt;li&gt;If we do, &lt;strong&gt;why&lt;&#x2F;strong&gt;? How does this affect the product?&lt;&#x2F;li&gt;
&lt;li&gt;Is there something &lt;strong&gt;easier&lt;&#x2F;strong&gt; we could work on that produces a similar
result?&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;This was not a compulsary exercise. It wasn&#x27;t as if all of us in the meeting
room had to go through these questions one by one and agree on answers. But it
was just something implicit that we had to keep in mind.&lt;&#x2F;p&gt;
&lt;p&gt;And in cases where working on something would lead to developers getting excited
(turns out one of the major reasons a developer would solve a problem is not
just because they &lt;strong&gt;need&lt;&#x2F;strong&gt; to, but because they &lt;strong&gt;want&lt;&#x2F;strong&gt; to), he would make it a
point to calm them (us) down and make sure we&#x27;re not losing track of the bigger
picture.&lt;&#x2F;p&gt;
&lt;p&gt;It turns out that by answering just these three questions, you not only make
your life much easier, but you also save your company a ton of time.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;1-do-we-need-to-do-this-at-all&quot;&gt;1. Do we need to do this at all?&lt;&#x2F;h2&gt;
&lt;p&gt;Sometimes, problems are not worth solving. Or in other words, problems are not
worth you spending your time on, for a variety of reasons. It could be that it&#x27;s
not affecting enough customers, or it&#x27;s a nice-to-have and your customers won&#x27;t
cancel their subscription if you didn&#x27;t implement this feature, or something
else.&lt;&#x2F;p&gt;
&lt;p&gt;As &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;kalzumeus.com&#x2F;2011&#x2F;10&#x2F;28&#x2F;dont-call-yourself-a-programmer&#x2F;&quot;&gt;Patrick McKenzie says&lt;&#x2F;a&gt;, your job as a developer is not to write code, but to
solve problems. As a developer, most of the time you&#x27;re surrounded by other
developers. In such cases, it&#x27;s easy to lose track of the bigger picture. And
when that happens, you might end up working on something which doesn&#x27;t need to
be solved. This counts as a waste on almost all kinds of productivity metrics.&lt;&#x2F;p&gt;
&lt;p&gt;When you ask whether we need to do something &lt;strong&gt;at all&lt;&#x2F;strong&gt;, you gain perspective.
You understand that the thing you&#x27;re being asked to do might not need to be
solved at all, or at least not in the near future.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;2-if-we-do-why-how-does-this-affect-the-product&quot;&gt;2. If we do, why? How does this affect the product?&lt;&#x2F;h2&gt;
&lt;p&gt;Identifying the origin of a problem is a critical skill. Some problems need
solutions as soon as possible, while some others are symptoms of something else
entirely. In such cases, it&#x27;s better to dig a bit deeper to find out what the
root cause is and work on that instead.&lt;&#x2F;p&gt;
&lt;p&gt;If you can identify where exactly a problem is coming from, and what does the
user really want to do when they&#x27;re asking for something, it becomes easier for
you to make sure that you&#x27;re solving the right problem and it does have a
sizeable impact on your product.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;3-is-there-something-easier-we-could-work-on-that-produces-a-similar-result&quot;&gt;3. Is there something easier we could work on that produces a similar result?&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;www.theeffectiveengineer.com&quot;&gt;Edmond Lau&lt;&#x2F;a&gt;, in his book &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.goodreads.com&#x2F;book&#x2F;show&#x2F;25238425-the-effective-engineer&quot;&gt;The Effective Engineer&lt;&#x2F;a&gt; defines a metric called
&quot;Leverage&quot;, which is directly proportional to the impact and inversely
proportional to the time investment.&lt;&#x2F;p&gt;
&lt;p&gt;In his equation, if you can decrease the &quot;time&quot; investment, but keep the
&quot;impact&quot; more or less constant by working on something else that produces a
similar result, you would be able to achieve similar-ish results in less time.&lt;&#x2F;p&gt;
&lt;p&gt;I find this an extremely effective framework using which feature development
work should be evaluated before starting implementation.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Encrypting Sensitive Data in SaltStack</title>
        <published>2018-01-05T00:00:00+00:00</published>
        <updated>2018-01-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/encrypting-sensitive-data-in-saltstack/"/>
        <id>https://sgoel.dev/posts/encrypting-sensitive-data-in-saltstack/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/encrypting-sensitive-data-in-saltstack/">&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.saltstack.com&#x2F;en&#x2F;latest&#x2F;&quot;&gt;SaltStack&lt;&#x2F;a&gt;, like any good configuration management system, provides a way to
distribute sensitive data (like passwords or API keys) to minion nodes in a
secure manner. This is supported by the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.saltstack.com&#x2F;en&#x2F;latest&#x2F;topics&#x2F;tutorials&#x2F;pillar.html&quot;&gt;Pillar&lt;&#x2F;a&gt; system, where this data can be
added to a tree-like data structure and are then made available to the relevant
minions.  While this &quot;distribution&quot; of data is secure, &quot;adding&quot; pillar entries
still works (at least by default) by editing YAML-formatted plain text files.
This means places like the salt master node and the source code repository
containing the state&#x2F;pillar tree are a weak spot, since there&#x27;s no at-rest
encryption for the included pillars.&lt;&#x2F;p&gt;
&lt;p&gt;One solution is to encrypt the pillars using GPG. This requires &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;gnupg.org&#x2F;&quot;&gt;GnuPG&lt;&#x2F;a&gt;, and
works using a public&#x2F;private keypair where the secrets are encrypted using the
public key, and the salt master decrypts them using the private key.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;gpg-renderer&quot;&gt;GPG Renderer&lt;&#x2F;h2&gt;
&lt;p&gt;Salt supports using &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.saltstack.com&#x2F;en&#x2F;latest&#x2F;ref&#x2F;renderers&#x2F;index.html&quot;&gt;renderers&lt;&#x2F;a&gt; which define how data from a given file should
be extracted. By default this is set to &lt;code&gt;yaml&lt;&#x2F;code&gt;, but renderers can also be
combined using pipes (&lt;code&gt;|&lt;&#x2F;code&gt;). So something like &lt;code&gt;yaml|gpg&lt;&#x2F;code&gt; means pass the input
first to the YAML renderer, take the output, and then feed it to the GPG
renderer.&lt;&#x2F;p&gt;
&lt;p&gt;The GPG renderer decrypts GPG ciphers. It works by first generating a
public&#x2F;private keypair, and then using the public key to encrypt secrets.
These encrypted values are then added to the pillar tree instead of the plain
text secrets. The salt master node can then decrypt these values using the
private key. This ensures that the pillar data is also encrypted at rest.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;generating-keys&quot;&gt;Generating keys&lt;&#x2F;h2&gt;
&lt;p&gt;The first step is to generate the keypair that will be used for
encrypting&#x2F;decrypting secrets. The following commands (and the rest of the
commands in this article) should be run on the salt master node.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; sudo mkdir &#x2F;etc&#x2F;salt&#x2F;gpgkeys&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; sudo chmod&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 0700&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &#x2F;etc&#x2F;salt&#x2F;gpgkeys&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; sudo gpg&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --gen-key --homedir&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &#x2F;etc&#x2F;salt&#x2F;gpgkeys&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now that the keypair is generated, we can export the public key that will be
used to encrypt pillars.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; sudo gpg&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --homedir&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &#x2F;etc&#x2F;salt&#x2F;gpgkeys&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --export --armor&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &#x2F;path&#x2F;to&#x2F;public.key&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Placing this public key alongside the state&#x2F;pillar tree in version control is a
good idea, so that other developers on the team can add secrets too.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;adding-encrypted-pillars&quot;&gt;Adding encrypted pillars&lt;&#x2F;h2&gt;
&lt;p&gt;To encrypt stuff, the public key needs to first be imported into the development
environment.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; gpg&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &#x2F;path&#x2F;to&#x2F;public.key&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;gpg&lt;&#x2F;code&gt; command can now be used to encrypt values.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; echo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;hunter2&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; gpg&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --armor --batch --trust-model&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; always&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; --encrypt -r&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; key-name&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This prints out a long and cryptic looking string on the terminal, which is the
encrypted secret that should be added to the pillar tree.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;#!yaml|gpg&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;app&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #85E89D;&quot;&gt;    key&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        -----BEGIN PGP MESSAGE-----&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        Version: GnuPG v1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        hQEMA0JzlBGmNEs6AQgAtuhy4tfowtXBXZTEQF1ZFZSvif7pvStK1SfdynbUmp7A&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        DLX9Xs1QeFlsgWmooZIN31f&#x2F;XR9enUGzkfqjECuys8flvL0zS4FmDnsqUipd4l6E&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        PLe0sAYEBZITidMxlru1mSbzn3CSGuUnDmfhPd7AYcGaMxIZHwb3OYgijpuv8Gzs&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        fCFzYHP&#x2F;vlovVFcpMVYCKjztPRng66akWeCI0uu9t8FBxQNWYFMBlzHrNyKSPrEt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        q0vSIkS+lR+te8MKCVO+4KCas7UiKXfJ3pI0faigcRFyzX6FkwEI1xZgV+V9Rbun&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        Df2+zEax+YUk2YSy5S4rIb8qB6A0zskxGdfKvF9KB9JCAXoti6zde7iqhbu6&#x2F;V92&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        rupyOWaJpRUXEdqb2ANrR2aQNSv+iA06bjwBMdixfaLWQ7TtyxuWjl9X4fQkwnvK&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        FEYW&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        =QHlJ&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;        -----END PGP MESSAGE-----&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;With some delay (that Salt takes to refresh pillars automatically), requesting
the pillar value for &lt;code&gt;app:key&lt;&#x2F;code&gt; should now show the decrypted value.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; sudo salt &amp;#39;*&amp;#39; pillar.get app:key&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;test-minion:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    hunter2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Handling the “N + 1 selects” problem in SQLAlchemy</title>
        <published>2017-12-17T00:00:00+00:00</published>
        <updated>2017-12-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/handling-the-n-1-selects-problem-in-sqlalchemy/"/>
        <id>https://sgoel.dev/posts/handling-the-n-1-selects-problem-in-sqlalchemy/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/handling-the-n-1-selects-problem-in-sqlalchemy/">&lt;p&gt;The &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;97197&#x2F;what-is-n1-select-query-issue&quot;&gt;&quot;N + 1&quot; selects&lt;&#x2F;a&gt; problem is one of the most common problems one might run
into when working on a database-backed web application. In this blog post, I&#x27;ll
describe what this problem exactly is, how &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Object-relational_mapping&quot;&gt;ORM&lt;&#x2F;a&gt;s (Object Relational Mappers) come
into the picture, and how we can work around 2 variants of these problems when
using &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;www.sqlalchemy.org&#x2F;&quot;&gt;SQLAlchemy&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;orms&quot;&gt;ORMs&lt;&#x2F;h2&gt;
&lt;p&gt;Most web application frameworks these days provide some sort of an ORM to handle
database queries. ORMs can be a double-edged sword. On the one hand they take
care of a lot of common database-related issues. On the other hand, using an ORM
doesn&#x27;t mean you can forget about how the underlying database actually works.&lt;&#x2F;p&gt;
&lt;p&gt;A personal rule of thumb is - if an ORM is not focusing on hiding the database
layer, it&#x27;s most probably good to use.&lt;&#x2F;p&gt;
&lt;p&gt;In this blog post, we&#x27;ll be using SQLAlchemy, which is an excellent database
toolkit and ORM for Python applications. It has a vast API - I&#x27;ve been working
with SQLAlchemy for a bit more than 5 years now and I can&#x27;t recall a single
instance where it didn&#x27;t solve a use case I had. On top of that, it does not
attempt to hide the database layer. Instead, it adds an abstraction layer on top
that makes all the database-interactions in Python applications much easier.&lt;&#x2F;p&gt;
&lt;p&gt;To me, SQLAlchemy feels very &quot;Pythonic&quot;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;description&quot;&gt;Description&lt;&#x2F;h2&gt;
&lt;p&gt;Consider an application which serves as an online books catalog, something like
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.goodreads.com&#x2F;&quot;&gt;Goodreads&lt;&#x2F;a&gt;. This most definitely involves having a &lt;code&gt;books&lt;&#x2F;code&gt; table, and an
&lt;code&gt;authors&lt;&#x2F;code&gt; table.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; BigInteger, Boolean, Column, ForeignKey, String&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy.ext.declarative&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; declarative_base&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy.orm&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; relationship&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Base&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; declarative_base()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Author&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Base&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    __tablename__&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;authors&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; Column(BigInteger,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; primary_key&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;True&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; Column(String(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;255&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; nullable&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;False&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; __init__&lt;&#x2F;span&gt;&lt;span&gt;(self, name):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        self&lt;&#x2F;span&gt;&lt;span&gt;.name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; name&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Book&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Base&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    __tablename__&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;books&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; Column(BigInteger,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; primary_key&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;True&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; Column(String(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;255&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; nullable&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;False&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    author_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; Column(BigInteger, ForeignKey(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;authors.id&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; nullable&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;False&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    is_bestseller&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; Column(Boolean,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; default&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;False&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    author&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; relationship(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;Author&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; backref&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;books&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; __init__&lt;&#x2F;span&gt;&lt;span&gt;(self, title, author):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        self&lt;&#x2F;span&gt;&lt;span&gt;.title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; title&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        self&lt;&#x2F;span&gt;&lt;span&gt;.author&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; author&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The two models are linked via the &lt;code&gt;author&lt;&#x2F;code&gt; relationship. This means that
accessing the &lt;code&gt;author&lt;&#x2F;code&gt; property on a &lt;code&gt;Book&lt;&#x2F;code&gt; object would return the associated
&lt;code&gt;Author&lt;&#x2F;code&gt; object. Since we&#x27;ve defined a &lt;code&gt;backref&lt;&#x2F;code&gt;, accessing the &lt;code&gt;books&lt;&#x2F;code&gt; property
for an &lt;code&gt;Author&lt;&#x2F;code&gt; object would return a &lt;code&gt;list&lt;&#x2F;code&gt; of &lt;code&gt;Book&lt;&#x2F;code&gt; objects that this author
wrote.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;variant-1-loading-objects&quot;&gt;Variant 1 - loading objects&lt;&#x2F;h2&gt;
&lt;p&gt;SQLAlchemy defines a bunch of strategies about how such related objects should
be loaded from the database. The default is set to &lt;code&gt;lazy&lt;&#x2F;code&gt;, which means fetching
a &lt;code&gt;Book&lt;&#x2F;code&gt; would not immediately result in fetching the &lt;code&gt;Author&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;To see this in action, the database engine needs to first be configured to echo
all the queries being executed.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy.engine&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; create_engine&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy.orm&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; sessionmaker&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;engine&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; create_engine(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;sqlite:&#x2F;&#x2F;&#x2F;:memory:&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; echo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;True&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;session_factory&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; sessionmaker(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;bind&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;engine)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If we now load a &lt;code&gt;book&lt;&#x2F;code&gt; from the database and check its &lt;code&gt;author&lt;&#x2F;code&gt;,&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;In [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]: book&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; session.query(Book).get(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2017&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;12&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;17 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt;9&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;23&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;39&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;833 INFO&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy.engine.base.Engine BEGIN (implicit)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2017&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;12&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;17 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt;9&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;23&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;39&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;833 INFO&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy.engine.base.Engine&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; SELECT&lt;&#x2F;span&gt;&lt;span&gt; books.id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; books_id, books.title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; books_title, books.author_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; books_author_id, books.is_bestseller&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; books_is_bestseller&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span&gt; books&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;WHERE&lt;&#x2F;span&gt;&lt;span&gt; books.id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt; ?&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2017&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;12&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;17 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt;9&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;23&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;39&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;833 INFO&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy.engine.base.Engine (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;In [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;]: author&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; book.author&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2017&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;12&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;17 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt;9&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;23&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;43&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;586 INFO&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy.engine.base.Engine&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; SELECT&lt;&#x2F;span&gt;&lt;span&gt; authors.id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; authors_id, authors.name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; authors_name&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span&gt; authors&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;WHERE&lt;&#x2F;span&gt;&lt;span&gt; authors.id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt; ?&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2017&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;12&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;17 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt;9&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;23&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;43&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;586 INFO&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy.engine.base.Engine (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;it can be seen that SQLAlchemy is lazy-loading the &lt;code&gt;author&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This is what the N + 1 problem is. If related objects are lazy loaded, the ORM
has to execute extra queries to load them. And if the original resultset is
large (assume 100 objects), then this involves one additional query for each
original object.&lt;&#x2F;p&gt;
&lt;p&gt;Fortunately, SQLAlchemy lets you specify what kind of loading strategy to use,
all of which are extensively documented &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;docs.sqlalchemy.org&#x2F;en&#x2F;latest&#x2F;orm&#x2F;loading_relationships.html&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;We can ask SQLAlchemy to load related objects using a &lt;code&gt;JOIN&lt;&#x2F;code&gt; statement,
modifying the &lt;code&gt;author&lt;&#x2F;code&gt; relationship definition as follows.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Book&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Base&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    # ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    author&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; relationship(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;Author&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; backref&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;books&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt; lazy&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;joined&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Defining the &lt;code&gt;author&lt;&#x2F;code&gt; relationship this way tells SQLAlchemy that every time a
&lt;code&gt;Book&lt;&#x2F;code&gt; is loaded, the related &lt;code&gt;Author&lt;&#x2F;code&gt; should be loaded as well via a &lt;code&gt;LEFT OUTER JOIN&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;In [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]: book&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; session.query(Book).get(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2017&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;12&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;17 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt;9&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;51&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt;34&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; INFO&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy.engine.base.Engine&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; SELECT&lt;&#x2F;span&gt;&lt;span&gt; books.id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; books_id, books.title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; books_title, books.author_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; books_author_id, books.is_bestseller&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; books_is_bestseller, authors_1.id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; authors_1_id,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;authors_1.name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; authors_1_name&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span&gt; books&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; LEFT OUTER JOIN&lt;&#x2F;span&gt;&lt;span&gt; authors&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; authors_1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; ON&lt;&#x2F;span&gt;&lt;span&gt; authors_1.id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; books.author_id&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;WHERE&lt;&#x2F;span&gt;&lt;span&gt; books.id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt; ?&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2017&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;12&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;17 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt;9&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;51&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt;34&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; INFO&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy.engine.base.Engine (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In case we don&#x27;t want to load related &lt;code&gt;Author&lt;&#x2F;code&gt;s on every &lt;code&gt;Book&lt;&#x2F;code&gt; fetch, this can
also be done on-demand using &lt;code&gt;sqlalchemy.orm.joinedload&lt;&#x2F;code&gt; when writing the
database query.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy.orm&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; joinedload&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;books&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; session.query(Book).\&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    options(joinedload(Book.author)).\&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    filter&lt;&#x2F;span&gt;&lt;span&gt;(Book.id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 42&lt;&#x2F;span&gt;&lt;span&gt;).\&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    one()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;variant-2-loading-scalars&quot;&gt;Variant 2 - loading scalars&lt;&#x2F;h2&gt;
&lt;p&gt;What if there was the use case that we&#x27;d like to load &lt;code&gt;Author&lt;&#x2F;code&gt; from the
database, and for each author, we&#x27;d like to know how many bestsellers they
wrote.&lt;&#x2F;p&gt;
&lt;p&gt;We could define a &lt;code&gt;property&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; func&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy.orm&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; object_session&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy.sql&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; and_&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Author&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Base&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;property&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; bestseller_count&lt;&#x2F;span&gt;&lt;span&gt;(self):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span&gt; object_session(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;).query(func.count(Book.id)).\&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;            filter&lt;&#x2F;span&gt;&lt;span&gt;(and_(Book.author_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; self&lt;&#x2F;span&gt;&lt;span&gt;.id,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                        Book.is_bestseller.is_(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;True&lt;&#x2F;span&gt;&lt;span&gt;))).\&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            scalar()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Accessing the &lt;code&gt;bestseller_count&lt;&#x2F;code&gt; property on &lt;code&gt;Author&lt;&#x2F;code&gt; objects is now going to
return the total number of bestseller books they wrote.&lt;&#x2F;p&gt;
&lt;p&gt;This works, but we still run into the N + 1 problem.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;In [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]: author&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; session.query(Author).get(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2017&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;12&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;17 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt;9&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;58&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;20&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;179 INFO&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy.engine.base.Engine&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; SELECT&lt;&#x2F;span&gt;&lt;span&gt; authors.id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; authors_id, authors.name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; authors_name&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span&gt; authors&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;WHERE&lt;&#x2F;span&gt;&lt;span&gt; authors.id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt; ?&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2017&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;12&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;17 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt;9&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;58&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;20&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;179 INFO&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy.engine.base.Engine (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;In [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;]:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; print&lt;&#x2F;span&gt;&lt;span&gt;(author.bestseller_count)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2017&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;12&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;17 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt;9&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;58&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;20&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;180 INFO&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy.engine.base.Engine&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; SELECT&lt;&#x2F;span&gt;&lt;span&gt; count(books.id)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; count_1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span&gt; books&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;WHERE&lt;&#x2F;span&gt;&lt;span&gt; books.author_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt; ?&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AND&lt;&#x2F;span&gt;&lt;span&gt; books.is_bestseller&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; IS 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2017&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;12&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;17 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt;9&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;58&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;20&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;180 INFO&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy.engine.base.Engine (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If we load 100 authors and then access the &lt;code&gt;bestseller_count&lt;&#x2F;code&gt; property on all of
them, this means a 100 extra selects being executed.&lt;&#x2F;p&gt;
&lt;p&gt;There are multiple ways to solve this problem, but one solution is to define a
&lt;code&gt;sqlalchemy.orm.column_property&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; func, select&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy.orm&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; column_property&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy.sql&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; and_&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; Author&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;Base&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    bestseller_count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; column_property(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        select([func.count(Book.id)]).\&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        where(and_(Book.author_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; id&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                   Book.is_bestseller.is_(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;True&lt;&#x2F;span&gt;&lt;span&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This asks SQLAlchemy to map the &lt;code&gt;bestseller_count&lt;&#x2F;code&gt; similar to how it defines a
normal table &lt;code&gt;Column&lt;&#x2F;code&gt;. Defining it this way makes SQLAlchemy load the count in
the original query.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;In [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]: author&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; session.query(Author).get(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2017&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;12&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;17 10&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt;7&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt;07&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; INFO&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy.engine.base.Engine SELECT (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;SELECT&lt;&#x2F;span&gt;&lt;span&gt; count(books.id)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; count_1 &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span&gt; books &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;WHERE&lt;&#x2F;span&gt;&lt;span&gt; books.author_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; authors.id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AND&lt;&#x2F;span&gt;&lt;span&gt; books.is_bestseller&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; IS 1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; anon_1, authors.id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; authors_id, authors.name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; authors_name &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span&gt; authors &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;WHERE&lt;&#x2F;span&gt;&lt;span&gt; authors.id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt; ?&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2017&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;12&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;17 10&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt;7&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FDAEB7;font-style: italic;&quot;&gt;07&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; INFO&lt;&#x2F;span&gt;&lt;span&gt; sqlalchemy.engine.base.Engine (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And that&#x27;s pretty cool.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;Like I mentioned earlier, you need to be careful when using ORMs. They make life
a LOT easier, but you might still run into edge-cases like this when an innocent
property access can end up sending a lot more queries to your database.&lt;&#x2F;p&gt;
&lt;p&gt;Also, before ending the blog post, I need to mention that joined loading is
&lt;strong&gt;not&lt;&#x2F;strong&gt; the solution in all cases. Each and every loading strategy that
SQLAlchemy supports is good for some cases and bad for some others. Luckily, the
SQLAlchemy documentation on the different &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;docs.sqlalchemy.org&#x2F;en&#x2F;latest&#x2F;orm&#x2F;loading_relationships.html&quot;&gt;relationship loading techniques&lt;&#x2F;a&gt; is
an excellent resource.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Speeding up Python using Cython</title>
        <published>2017-11-30T00:00:00+00:00</published>
        <updated>2017-11-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/speeding-up-python-using-cython/"/>
        <id>https://sgoel.dev/posts/speeding-up-python-using-cython/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/speeding-up-python-using-cython/">&lt;p&gt;If you&#x27;ve worked with Python before, you probably know that it makes a trade-off
between developer productivity and speed of execution. To keep you as productive
as possible, it does a lot of stuff behind the scenes. But this &quot;stuff&quot; comes at
a cost.&lt;&#x2F;p&gt;
&lt;p&gt;From within the language, there are mainly 2 ways of speeding up performance
critical pieces of code. You can either use the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.python.org&#x2F;3&#x2F;c-api&#x2F;index.html&quot;&gt;Python&#x2F;C API&lt;&#x2F;a&gt;, write your code
as a C extension, compile it down to machine code, and then use it in Python.
Alternatively, you could write the performance critical functions in pure C (or
in another compiled language), compile them to a shared library and use &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.python.org&#x2F;3.6&#x2F;library&#x2F;ctypes.html&quot;&gt;ctypes&lt;&#x2F;a&gt;
to access them in Python space.&lt;&#x2F;p&gt;
&lt;p&gt;Both the options work really well. The only problem is, you need to write C. And
when you write C, you immediately lose the productivity you gained when you
switched to Python.&lt;&#x2F;p&gt;
&lt;p&gt;Fortunately, there&#x27;s a third option called &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;cython.org&quot;&gt;Cython&lt;&#x2F;a&gt;, that lets you write
Python-ish code without having to sacrifice performance.&lt;&#x2F;p&gt;
&lt;p&gt;This blog post will focus on how I used Cython to improve the performance of an
open source package &lt;a href=&quot;https:&#x2F;&#x2F;sgoel.dev&#x2F;posts&#x2F;streaming-multipart-form-data-parser-for-python&#x2F;&quot;&gt;I recently wrote&lt;&#x2F;a&gt; by upto 95%.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;background&quot;&gt;Background&lt;&#x2F;h2&gt;
&lt;p&gt;The package in concern is &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;streaming-form-data&quot;&gt;streaming-form-data&lt;&#x2F;a&gt;. Mostly intended to be used in
Python web frameworks - specifically request handlers (views) that handle file
uploads - it&#x27;s a small library that lets you parse &lt;code&gt;multipart&#x2F;form-data&lt;&#x2F;code&gt; encoded
content in a streaming manner. This means that if you&#x27;re accepting file uploads
from an HTML &lt;code&gt;&amp;lt;form&amp;gt;&lt;&#x2F;code&gt;, you don&#x27;t need to wait for the entire file to be loaded
into memory before doing any work. You can basically work on the input chunk by
chunk.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;problem&quot;&gt;Problem&lt;&#x2F;h2&gt;
&lt;p&gt;The way the parser works is that it looks at each byte in the request body,
looking for occurences of the &lt;code&gt;boundary&lt;&#x2F;code&gt; string, which is specified in the
&lt;code&gt;Content-Disposition&lt;&#x2F;code&gt; HTTP header (there&#x27;s a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.w3.org&#x2F;TR&#x2F;html401&#x2F;interact&#x2F;forms.html&quot;&gt;W3C article&lt;&#x2F;a&gt; that describes this
in much more detail). An occurence of such a boundary string means that the
data for one input field has ended, after which either data for the next input
field can start, or it signifies end of content (in which case the boundary is
slightly different, but for now this level of abstraction should work).&lt;&#x2F;p&gt;
&lt;p&gt;The way this &quot;boundary finding&quot; works is through a utility &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;streaming-form-data&#x2F;blob&#x2F;acc045eabe9145001a4df3e360b7248436549720&#x2F;streaming_form_data&#x2F;_parser.pyx#L17&quot;&gt;Finder&lt;&#x2F;a&gt; class. Its
usage looks something like the following.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; streaming_form_data._parser&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; Finder&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;finder&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; Finder(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;needle&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; byte&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span&gt; haystack:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    finder.feed(byte)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; finder.found:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;Found!&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        finder.reset()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        break&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It takes the string to be found as a parameter, defines a &lt;code&gt;feed(byte)&lt;&#x2F;code&gt; function
to feed a single byte from the haystack, and a &lt;code&gt;found&lt;&#x2F;code&gt; property which tells you
whether or not it has found what it was looking for.&lt;&#x2F;p&gt;
&lt;p&gt;Since objects from this class are being used by the parser to determine whether
or not a boundary has been found, you can imagine that the &lt;code&gt;feed&lt;&#x2F;code&gt; function is
going to be called for &lt;strong&gt;every single byte&lt;&#x2F;strong&gt; from the input. If the input is
large, that&#x27;s a lot of calls! And of course, it shows! My initial implementation
took more than 3 seconds to parse a ~300kb file, maxing out the CPU the entire
time. 🤒&lt;&#x2F;p&gt;
&lt;h2 id=&quot;cython&quot;&gt;Cython&lt;&#x2F;h2&gt;
&lt;p&gt;Cython is an optimizing static compiler that compiles your Python code into C,
which you can further compile down to machine code to achieve basically native
performance. Cython is a superset of Python - this means that you can either
write your performance critical code in Python and then compile it using Cython,
or you could write your code using additional C-like constructs that Cython
offers and then do the &quot;Python -&amp;gt; C -&amp;gt; machine code&quot; dance. Either way, it&#x27;s
fascinating to see the amount of C code that Cython saves you from writing.&lt;&#x2F;p&gt;
&lt;p&gt;Since Cython is a superset of Python, I decided to try it out. I moved the
performance critical pieces of code to Cython, and wrote a wrapper class calling
the Cython code to do the actual parsing. And the results were stunning.&lt;&#x2F;p&gt;
&lt;p&gt;For the purposes of this blog post, I used the following function to benchmark
the Python code v&#x2F;s the Cython code.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; requests_toolbelt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; MultipartEncoder&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; _parse&lt;&#x2F;span&gt;&lt;span&gt;(Parser, filename, content_type)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    with&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; open&lt;&#x2F;span&gt;&lt;span&gt;(filename,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;rb&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; fd:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        fields&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            filename: (filename, fd, content_type)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        encoder&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; MultipartEncoder(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;fields&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;fields)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        parser&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; Parser(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFAB70;&quot;&gt;headers&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;Content-Type&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;: encoder.content_type})&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        parser.data_received(encoder.to_string())&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; py_parse&lt;&#x2F;span&gt;&lt;span&gt;(filename, content_type):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    _parse(PyParser, filename, content_type)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; cython_parse&lt;&#x2F;span&gt;&lt;span&gt;(filename, content_type):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    _parse(CythonParser, filename, content_type)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;On a 200 bytes text file, running &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ipython.org&#x2F;ipython-doc&#x2F;3&#x2F;interactive&#x2F;magics.html#magic-timeit&quot;&gt;timeit&lt;&#x2F;a&gt; gives the following results.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;In [9]: %timeit py_parse(&amp;#39;file.txt&amp;#39;, &amp;#39;application&#x2F;text)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;1.62 ms ± 3.72 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;In [10]: %timeit cython_parse(&amp;#39;file.txt&amp;#39;, &amp;#39;application&#x2F;text&amp;#39;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;219 µs ± 1.06 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Nice. That&#x27;s already a roughly 86% reduction in run time.&lt;&#x2F;p&gt;
&lt;p&gt;What happens on an 11kb image?&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;In [7]: %timeit py_parse(&amp;#39;image-11k.png&amp;#39;, &amp;#39;image&#x2F;png&amp;#39;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;63.7 ms ± 485 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;In [8]: %timeit cython_parse(&amp;#39;image-11k.png&amp;#39;, &amp;#39;image&#x2F;png&amp;#39;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2.35 ms ± 45 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That&#x27;s a 96% reduction in time - also very cool!&lt;&#x2F;p&gt;
&lt;p&gt;Now let&#x27;s increase the file size by a bit more and try out a 0.5mb image.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;In [11]: %timeit py_parse(&amp;#39;image-500k.png&amp;#39;, &amp;#39;image&#x2F;png&amp;#39;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2.33 s ± 13.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;In [12]: %timeit cython_parse(&amp;#39;image-500k.png&amp;#39;, &amp;#39;image&#x2F;png&amp;#39;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;80.8 ms ± 534 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;While the pure Python version takes more than 2 seconds, the Cython version
finishes the job in less than 100ms. This is very impressive.&lt;&#x2F;p&gt;
&lt;p&gt;Out of curiousity, I tried throwing a 10mb file at it. I waited for 1 minute
before killing the &lt;code&gt;%timeit py_parse(&#x27;image-10m.jpg&#x27;, &#x27;image&#x2F;jpg&#x27;)&lt;&#x2F;code&gt; call, while
the Cythonized version shows the following results:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;In [17]: %timeit cython_parse(&amp;#39;image-10m.jpg&amp;#39;, &amp;#39;image&#x2F;jpg&amp;#39;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;1.43 s ± 42.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Sure, 1.43s for a single 10mb file is still &lt;strong&gt;way&lt;&#x2F;strong&gt; too much. But this I feel has
nothing do with Cython, and is a shortcoming in the library itself. Like with
any other piece of code, there&#x27;s lots of work to be done before calling it a
day (&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;streaming-form-data&#x2F;pulls&quot;&gt;pull requests welcome&lt;&#x2F;a&gt;!).&lt;&#x2F;p&gt;
&lt;p&gt;Besides, the point of this blog post was to show what kind of speedups Cython
can bring to your code. 🙂&lt;&#x2F;p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;m fairly impressed by Cython. Not just because it&#x27;s a superset of Python and
is hence very easy to write and integrate. But also, it&#x27;s fascinating how much
amount of C code it saves you from writing. To get an idea, create a Python file
called &lt;code&gt;hello.py&lt;&#x2F;code&gt;, define a function that prints out &quot;Hello, World!&quot;, run
&lt;code&gt;cython hello.py&lt;&#x2F;code&gt;, and open up &lt;code&gt;hello.c&lt;&#x2F;code&gt; in an editor.&lt;&#x2F;p&gt;
&lt;p&gt;💯&#x2F;💯 would use again. 🙂&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Tornado + SQLAlchemy</title>
        <published>2017-08-27T00:00:00+00:00</published>
        <updated>2017-08-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/tornado-sqlalchemy/"/>
        <id>https://sgoel.dev/posts/tornado-sqlalchemy/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/tornado-sqlalchemy/">&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;www.tornadoweb.org&#x2F;en&#x2F;stable&#x2F;&quot;&gt;Tornado&lt;&#x2F;a&gt; is an asynchronous web framework, allowing you to handle web requests
asynchronously out of the box. &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;docs.sqlalchemy.org&#x2F;en&#x2F;latest&#x2F;&quot;&gt;SQLAlchemy&lt;&#x2F;a&gt; is an excellent database toolkit.
I&#x27;ve been using it for close to 5 years now and not once have I had a bad
experience with it.&lt;&#x2F;p&gt;
&lt;p&gt;Given the nature of both frameworks, it can be either really difficult to
integrate the two, or really easy. There&#x27;s no middle ground. You either need to
roll up your sleeves and go full-async, or you can make a few assumptions about
your application and write a small set of helper functions that assume the user
&lt;strong&gt;knows&lt;&#x2F;strong&gt; the two frameworks.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m lazy, so I chose the second option and wrote &lt;code&gt;tornado-sqlalchemy&lt;&#x2F;code&gt;. It&#x27;s a
small module that tries to make SQLAlchemy usage in Tornado applications a bit
easier.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re currently using both the frameworks in your application,
you probably wrote your own boilerplate code that establishes the database
connection, initializes the engine and then finally obtains the session. In
addition, you probably also wrote code to make sure that the session lifetime is
the same as the request lifetime.&lt;&#x2F;p&gt;
&lt;p&gt;Similarly, if you also have &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;alembic.zzzcomputing.com&#x2F;en&#x2F;latest&#x2F;&quot;&gt;alembic&lt;&#x2F;a&gt; in your stack, that means you likely have
&lt;strong&gt;some&lt;&#x2F;strong&gt; custom code written so that &lt;code&gt;alembic&lt;&#x2F;code&gt; has the correct &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;docs.sqlalchemy.org&#x2F;en&#x2F;latest&#x2F;core&#x2F;metadata.html#sqlalchemy.schema.MetaData&quot;&gt;MetaData&lt;&#x2F;a&gt; object.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;tornado-sqlalchemy&lt;&#x2F;code&gt; tries to address these issues by making one big fat
assumption - if you&#x27;re using SQLAlchemy and Tornado, you know how they work
internally. This means, you should know how that &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;www.tornadoweb.org&#x2F;en&#x2F;stable&#x2F;ioloop.html&quot;&gt;ioloop&lt;&#x2F;a&gt; works, how
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;docs.sqlalchemy.org&#x2F;en&#x2F;latest&#x2F;orm&#x2F;session_basics.html#when-do-i-construct-a-session-when-do-i-commit-it-and-when-do-i-close-it&quot;&gt;session handling&lt;&#x2F;a&gt; works, and how to work with &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;docs.sqlalchemy.org&#x2F;en&#x2F;latest&#x2F;core&#x2F;connections.html&quot;&gt;connection and engine&lt;&#x2F;a&gt; objects.&lt;&#x2F;p&gt;
&lt;p&gt;This results in a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;xkcd.com&#x2F;927&#x2F;&quot;&gt;standardized library&lt;&#x2F;a&gt; which can act as a central place for
all the &lt;s&gt;bugs&lt;&#x2F;s&gt; features, and hopefully can establish best practices.&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, the module is on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pypi.python.org&#x2F;pypi&#x2F;tornado-sqlalchemy&quot;&gt;PyPI&lt;&#x2F;a&gt; under the name &lt;code&gt;tornado-sqlalchemy&lt;&#x2F;code&gt;, and the
source is on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;tornado-sqlalchemy&quot;&gt;Github&lt;&#x2F;a&gt;. Hope it&#x27;s useful!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Stuck in Vim</title>
        <published>2017-06-27T00:00:00+00:00</published>
        <updated>2017-06-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/stuck-in-vim/"/>
        <id>https://sgoel.dev/posts/stuck-in-vim/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/stuck-in-vim/">&lt;p&gt;&lt;strong&gt;TL;DR: I built &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;stuckinvim.com&quot;&gt;http:&#x2F;&#x2F;stuckinvim.com&lt;&#x2F;a&gt;. Use it if you&#x27;re stuck in Vim.&lt;&#x2F;strong&gt; 🙂&lt;&#x2F;p&gt;
&lt;p&gt;It started when I was on a flight from Munich to Vancouver, which is a 10 hour
journey. We were probably somewhere above Iceland when I realized that the book
I had brought along with me for the flight wasn&#x27;t something I was going to enjoy
reading. And I wasn&#x27;t convinced by any of the movies offered by the in-flight
entertainment. And we still had a good 7-8 hours left. 😒&lt;&#x2F;p&gt;
&lt;p&gt;After a brief moment of being annoyed, it occured to me that just a day ago,
StackOverflow had published a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;stackoverflow.blog&#x2F;2017&#x2F;05&#x2F;23&#x2F;stack-overflow-helping-one-million-developers-exit-vim&#x2F;&quot;&gt;blog post&lt;&#x2F;a&gt; where they collected some statistics
and concluded that it was pretty difficult for new Vim users to exit Vim. How
difficult? So difficult, that at any given point of time, around 80 people are
viewing the question &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;11828270&#x2F;how-to-exit-the-vim-editor&quot;&gt;How to exit the Vim editor&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Now, I use Vim daily, and even though I&#x27;m far from an expert, that number is
still mind boggling to me. On the other hand, it kind-of-sort-of makes sense.
A few weeks back, I tried to switch to using &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;www.mutt.org&#x2F;&quot;&gt;mutt&lt;&#x2F;a&gt; as my primary email client.
Even though I could configure it successfully, I could barely get the hang of
it. So it&#x27;s completely reasonable that that scenario happens for Vim too.&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, back to the plane. After my mind had had some time to process all this,
I decided that making a webapp which helps people exit Vim might be a fun little
project. The only problem was that I had no internet, so I couldn&#x27;t really use
any external libraries. Or wait, maybe I have dist files somewhere on my laptop
from older projects? Turns out, I did. So I copied over the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;getbootstrap.com&quot;&gt;Bootstrap&lt;&#x2F;a&gt; and
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;vuejs.org&quot;&gt;Vue.js&lt;&#x2F;a&gt; static resources and got to work. After about an hour or so, I had a
basic prototype working, which asked you a bunch of questions about what state
in Vim you&#x27;re currently in (modeled as a finite state machine), and then tells
you the command you need to type if you want to exit.&lt;&#x2F;p&gt;
&lt;p&gt;The result can be seen here - &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;stuckinvim.com&quot;&gt;https:&#x2F;&#x2F;stuckinvim.com&lt;&#x2F;a&gt; 🙂&lt;&#x2F;p&gt;
&lt;p&gt;Since then, I&#x27;ve had some more time to polish this, share it with a few friends
and on reddit, and have gotten some good feedback. I open sourced the codebase
on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;stuckinvim.com&quot;&gt;Github&lt;&#x2F;a&gt; and have even had some people sending in pull requests!&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, I hope this app is useful. In case you find a bug, or some weird Vim
scenario missing, please feel free to either file an issue or even better, open
a pull request!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Streaming multipart&#x2F;form-data parser for Python</title>
        <published>2017-05-06T00:00:00+00:00</published>
        <updated>2017-05-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/streaming-multipart-form-data-parser-for-python/"/>
        <id>https://sgoel.dev/posts/streaming-multipart-form-data-parser-for-python/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/streaming-multipart-form-data-parser-for-python/">&lt;p&gt;Recently at work we were working on a feature where we wanted our users to be
able to upload files. We&#x27;re a Python shop using &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;www.tornadoweb.org&quot;&gt;tornado&lt;&#x2F;a&gt;, and tornado handles
it really well by parsing the form data and making the uploaded file available
directly inside the request handler. The catch is that by the time the handler
function is called, the complete file is in memory independent of its size.
The obvious fix is to set a limit on the request size in your reverse proxy
server (nginx&#x2F;apache&#x2F;...) so that any request exceeding the threshold size is
dropped and never reaches tornado.&lt;&#x2F;p&gt;
&lt;p&gt;That works really well in practice. However, sometimes it&#x27;s nicer to not be
forced to load the complete file in memory (even if there&#x27;s a limit on the file
size) and handle the file as data chunks are read from the HTTP request. Again,
tornado handles this really well with the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;www.tornadoweb.org&#x2F;en&#x2F;stable&#x2F;web.html#tornado.web.stream_request_body&quot;&gt;stream_request_body&lt;&#x2F;a&gt; decorator which
provides an interface to handle each chunk of the request body while it&#x27;s being
read from the reverse proxy.&lt;&#x2F;p&gt;
&lt;p&gt;The catch here is that &lt;code&gt;stream_request_body&lt;&#x2F;code&gt; works on the request level, while
file uploads are usually encoded using the multipart&#x2F;form-data encoding. This
means you can&#x27;t just throw in &lt;code&gt;stream_request_body&lt;&#x2F;code&gt; and expect things to work.
You need to parse the form body to be able to see the file.&lt;&#x2F;p&gt;
&lt;p&gt;This seemed like one of the problems which makes you think &quot;I bet someone
already ran into this problem and wrote a solution for it&quot;. But surprisingly,
nothing existed which could handle parsing multipart&#x2F;form-data encoded data in a
streaming manner.&lt;&#x2F;p&gt;
&lt;p&gt;So I spent some of my spare time writing &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;siddhantgoel&#x2F;streaming-form-data&quot;&gt;streaming-form-data&lt;&#x2F;a&gt;. This small
module provides a Python parser which expects successive request body chunks,
and lets you define what should be done with each field in the data. The way
it&#x27;s done is that you initialize the main parser class, and associate one of the
&lt;code&gt;*Target&lt;&#x2F;code&gt; classes to define what should be done with a field when it has been
extracted out of the request body. For instance, &lt;code&gt;FileTarget&lt;&#x2F;code&gt; would stream the
data to a file on disk, while &lt;code&gt;SHA256Target&lt;&#x2F;code&gt; calculates the sha256 hash of the
content seen so far.&lt;&#x2F;p&gt;
&lt;p&gt;Writing this parser also made me realize that byte-by-byte processing in pure
Python can be slow. So slow that running a 500k file through this parser took ~3
seconds to finish and hogged the CPU during the time. I spent some time trying
to move the core parsing code into a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.python.org&#x2F;3&#x2F;extending&#x2F;extending.html&quot;&gt;C extension&lt;&#x2F;a&gt;, but the amount of work
involved in defining a new type and handling bytes inputs (not to mention manual
memory management) always made me procrastinate.&lt;&#x2F;p&gt;
&lt;p&gt;Luckily, I found &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;cython.org&quot;&gt;Cython&lt;&#x2F;a&gt;, which lets me write Python code (well, a superset of
Python) and compiles it down to a C extension which I could then compile into a
shared library. This let me define a Python interface to the module while the
performance-intensive code was written in C. This was so cool, it almost felt
like cheating.&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, I released the module to PyPI under the name
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pypi.python.org&#x2F;pypi&#x2F;streaming-form-data&quot;&gt;streaming-form-data&lt;&#x2F;a&gt;.
Hope it&#x27;s useful.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Experiences using Terraform</title>
        <published>2017-03-09T00:00:00+00:00</published>
        <updated>2017-03-09T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/experiences-using-terraform/"/>
        <id>https://sgoel.dev/posts/experiences-using-terraform/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/experiences-using-terraform/">&lt;p&gt;I&#x27;ve been working a lot with &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.terraform.io&quot;&gt;terraform&lt;&#x2F;a&gt; recently, and it has been quite
productive so far. Sure, there are some rough edges, but on the whole it&#x27;s a
really nice piece of software that lets you express all your infrastructure as
code. The documentation does a fairly good job, but as with most software, there
are still &quot;gotchas&quot; you could run into that may catch you by surprise. Here are
some such things that I discovered, that I wish I knew before I started using
terraform.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Use modules.&lt;&#x2F;strong&gt; Really. It doesn&#x27;t matter if you think &quot;well, I definitely
wouldn&#x27;t need to use this piece of code somewhere else&quot;, because you most
certainly will. And in that case, it&#x27;s going to be a pain to extract your
configuration out into modules. To be fair, the process itself isn&#x27;t that
complicated, and terraform does provide you with tools to assist with the
process (&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.terraform.io&#x2F;docs&#x2F;commands&#x2F;state&#x2F;mv.html&quot;&gt;terraform state mv&lt;&#x2F;a&gt;), but it&#x27;s just a lot of effort that you don&#x27;t
have to put in if you use modules right from the beginning.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Just because &lt;code&gt;terraform plan&lt;&#x2F;code&gt; succeeded, doesn&#x27;t mean &lt;code&gt;terraform apply&lt;&#x2F;code&gt;
will.&lt;&#x2F;strong&gt; I&#x27;m mostly using AWS, so this may only be specific to AWS, but there are
some operations that the AWS API doesn&#x27;t allow, which &lt;code&gt;plan&lt;&#x2F;code&gt; wouldn&#x27;t catch. So
it&#x27;s entirely possible that if you get a green light from &lt;code&gt;plan&lt;&#x2F;code&gt;, the &lt;code&gt;apply&lt;&#x2F;code&gt;
fails somewhere in the middle because of a stupid 400 response from the AWS API.
Keep the &lt;code&gt;plan&lt;&#x2F;code&gt; and &lt;code&gt;apply&lt;&#x2F;code&gt; cycle short. Meaning, make small changes, &lt;code&gt;plan&lt;&#x2F;code&gt;
them, and &lt;code&gt;apply&lt;&#x2F;code&gt; them, to avoid having an error in the middle of a rather long
&lt;code&gt;apply&lt;&#x2F;code&gt;. But I guess this point is not specific to &lt;code&gt;terraform&lt;&#x2F;code&gt;, and applies to
software in general.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Keep &lt;code&gt;terraform&lt;&#x2F;code&gt; to strictly providing the backbone of your stack.&lt;&#x2F;strong&gt; This is a
personal opinion, but I think the work of terraform ends as soon as your
infrastructure is provisioned. Running scripts on, let&#x27;s say EC2 instances
provisioned via terraform, is OK, as long as those scripts are tiny and delegate
everything else to your configuration management system. So something like
&lt;code&gt;sudo apt-get install salt-minion &amp;amp;&amp;amp; .&#x2F;configure-salt.sh &amp;amp;&amp;amp; salt-call state.apply&lt;&#x2F;code&gt; should be OK in the user data, but more than that is just asking
for trouble.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Decide on a naming convention for your resources early.&lt;&#x2F;strong&gt; Having different
styles in resource names is an eye-sore, and can be very confusing. Are you
prepending your application name to each resource? Or appending? Are you using
underscores? Hyphens? Spaces? Emojis? Deciding this one early enough in your
project is definitely going to help.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Error messages don&#x27;t always make sense.&lt;&#x2F;strong&gt; This one is not always true (hah!),
but at times I do see errors that don&#x27;t completely make sense and might be
because of a stray &lt;code&gt;}&lt;&#x2F;code&gt; somewhere, or a missing parameter which may not be
obvious.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Be careful of what you&#x27;re committing.&lt;&#x2F;strong&gt; Avoid committing the &lt;code&gt;.tfstate&lt;&#x2F;code&gt; and
&lt;code&gt;.tfstate.backup&lt;&#x2F;code&gt; files to git. This doesn&#x27;t mean throw them away. But know
that these would contain your application secrets, so having them in your git
repository is probably not the best idea.&lt;&#x2F;p&gt;
&lt;p&gt;Lastly, and this is not really a gotcha. But &lt;code&gt;terraform&lt;&#x2F;code&gt; by itself is a very
flexible tool, so it&#x27;s really up to you how you want to use it. You might find a
ton of different advice on the internet about, let&#x27;s say, adding a staging
environment to your infrastructure. And they&#x27;ll all probably be right. But your
setup might be different, and that&#x27;s OK. As long as you know what you&#x27;re doing
and have read the documentation, &lt;code&gt;terraform&lt;&#x2F;code&gt; is really a joy to work with.&lt;&#x2F;p&gt;
&lt;hr&#x2F;&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;&#x2F;strong&gt;: As pointed out in a comment on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;devops&#x2F;comments&#x2F;6gt547&#x2F;experiences_using_terraform&#x2F;diuecht&#x2F;&quot;&gt;reddit&lt;&#x2F;a&gt;, the &lt;code&gt;.tfstate&lt;&#x2F;code&gt; files
contain secrets only in case the provisioned resources involve secrets. So your
RDS root password would end up in the state file, but if you don&#x27;t provision
any such resource, you&#x27;re probably OK. The point is - be careful of what you&#x27;re
committing to Git!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>ELB proxy protocol health check</title>
        <published>2017-01-23T00:00:00+00:00</published>
        <updated>2017-01-23T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/elb-proxy-protocol-health-check/"/>
        <id>https://sgoel.dev/posts/elb-proxy-protocol-health-check/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/elb-proxy-protocol-health-check/">&lt;p&gt;&lt;strong&gt;Situation&lt;&#x2F;strong&gt;: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;aws.amazon.com&#x2F;elasticloadbalancing&#x2F;&quot;&gt;ELB&lt;&#x2F;a&gt; configured with TCP listeners, passing traffic on to &lt;code&gt;nginx&lt;&#x2F;code&gt;
which then &lt;code&gt;proxy_pass&lt;&#x2F;code&gt;es all traffic to the actual application server. Since we
want &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;blog.seafuj.com&#x2F;using-elb-with-websockets&quot;&gt;websockets to load balance properly&lt;&#x2F;a&gt;, proxy protocol has been enabled on
both &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.aws.amazon.com&#x2F;elasticloadbalancing&#x2F;latest&#x2F;classic&#x2F;enable-proxy-protocol.html&quot;&gt;ELB&lt;&#x2F;a&gt; and &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.nginx.com&#x2F;resources&#x2F;admin-guide&#x2F;proxy-protocol&#x2F;&quot;&gt;nginx&lt;&#x2F;a&gt;. And since we want to get emails when stuff is not
working, there&#x27;s an HTTP health check setup on the ELB which accesses the same
port on which nginx is listening.&lt;&#x2F;p&gt;
&lt;p&gt;Can you spot the mistake?&lt;&#x2F;p&gt;
&lt;p&gt;Enabling proxy protocol on the ELB does not automatically translate to
health checks. That is, even though the normal requests would be getting the
fancy proxy protocol line prepended in front of every request, it&#x27;s going to be
missing from the health check requests. Which means that soon, the load balancer
is going to start taking instances out, thinking that they&#x27;re no longer healthy.
Of course, all the instances are still healthy. They just aren&#x27;t receiving the
extra proxy protocol info.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;&#x2F;strong&gt;?&lt;&#x2F;p&gt;
&lt;p&gt;Switch the health check to TCP. This only checks whether or not a TCP connection
can be successfully established (which might be OK for some cases). But not so
much in case you want the health check to check the health of your application.&lt;&#x2F;p&gt;
&lt;p&gt;Configuring the health check to directly access your application server over
HTTP would also work.&lt;&#x2F;p&gt;
&lt;p&gt;Alternatively, just use &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;aws.amazon.com&#x2F;elasticloadbalancing&#x2F;applicationloadbalancer&#x2F;&quot;&gt;ALB&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Non-technical reasons why I like Vue</title>
        <published>2016-11-13T00:00:00+00:00</published>
        <updated>2016-11-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/non-technical-reasons-why-i-like-vue/"/>
        <id>https://sgoel.dev/posts/non-technical-reasons-why-i-like-vue/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/non-technical-reasons-why-i-like-vue/">&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;vuejs.org&quot;&gt;Vue.js&lt;&#x2F;a&gt; has been getting a fair amount of press recently. Most of it is from
people who write frontend code for a living. I&#x27;ve instead spent most of my
professional life working with backend &amp;amp; infrastructure code so I consider
myself a new entrant to the JS world. I do have some memories writing JS, but they
go back to the days when Rails 2.3 was still new and jQuery was the hot new thing.
Which means in those days you could drop in a script tag, wrap everything around
in &lt;code&gt;$(document).ready&lt;&#x2F;code&gt; and no one would raise an eyebrow.&lt;&#x2F;p&gt;
&lt;p&gt;Of course, the nature and complexity of applications being developed has changed
since then. Simple callbacks don&#x27;t suffice now and we need more sophisticated
tools to get the job done. There are hundreds of frameworks out there, and many
more tutorials, each picking up a different permutation of frameworks&#x2F;tools. But
I think the worst part is that every project claims to do things better than
every other project. And as a new entrant to the ecosystem, you don&#x27;t know who
to trust and how to find out. Probably this also contributes to
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@ericclemmons&#x2F;javascript-fatigue-48d4011b6fc4#.rm115vjs9&quot;&gt;Javascript fatigue&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;A lot of people give all sorts of technical reasons why Vue is better than X. I
won&#x27;t do that. Partly because the Vue &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;vuejs.org&#x2F;v2&#x2F;guide&#x2F;comparison.html&quot;&gt;comparison guide&lt;&#x2F;a&gt; does that already. And
partly because I think every project is better than every other project at
serving at least one use case. So comparing engineering approaches does not feel
right. Instead, I list out the top 3 non-technical reasons why I like working
with Vue.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;cognitive-load&quot;&gt;Cognitive load&lt;&#x2F;h2&gt;
&lt;p&gt;Frameworks are supposed to make your life easier, by giving you a set of tools
and then letting you think about your application logic. To me, that sounds like
the reason why frameworks exist. And the lower the surface area of the API is
(another metric instead of surface area might be how intuitive the API is),
the easier it is to keep all of it in your head. This, in turn, frees up your
brain to think about the actual problem at hand. And this is exactly the first
reason I like Vue. The API by itself is tiny. You have the HTML views, &lt;code&gt;data&lt;&#x2F;code&gt;,
&lt;code&gt;methods&lt;&#x2F;code&gt;, and &lt;code&gt;computed&lt;&#x2F;code&gt; properties. And that&#x27;s basically it. You can skim
through the documentation in less than half a day and basically start writing
application code.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;decisions&quot;&gt;Decisions&lt;&#x2F;h2&gt;
&lt;p&gt;We make decisions all day, and every decision we make costs brain power. I&#x27;ve
often noticed that the more decisions I have to make, the worse is my ability to
make more decisions. A JS framework these days has to provide more than just
the view layer. The fact that Vue provides &quot;official recommendations&quot; on what
software to pick and how to setup tools to organize your build pipeline saves me
so many decision tokens. For a new entrant, it can be very overwhelming to setup
the entire build process before even getting to write some application code. And
if the framework helps you do that - excellent!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;no-strict-enforcement&quot;&gt;No strict enforcement&lt;&#x2F;h2&gt;
&lt;p&gt;The third, and slightly technical reason is that Vue lets my application evolve.
It somehow hits the sweet spot between adding callbacks to server-side rendered
pages, and a complete SPA website. I can pull it towards the jQuery end of
things and just drop in a script tag and write some tiny logic that keeps some
elements in sync with some others. And when my application complexity grows, I
can also pull the framework towards the React side of things, and organize
everything around components to introduce some sanity. Nothing is enforced from
the beginning. The evolution is completely organic.&lt;&#x2F;p&gt;
&lt;p&gt;At the end of the day, we all want to solve problems. Your application could
&lt;em&gt;always&lt;&#x2F;em&gt; be better from an engineering perspective. But finding that sweet spot
when it&#x27;s engineered well enough and also solves business needs can be
difficult. And somehow Vue helped me get there faster than anything else could.
That&#x27;s not to say that it&#x27;s not achievable using other frameworks. It basically
comes down to choosing the right tool for the job keeping in mind the context
constraints.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Projects</title>
        <published>2015-06-05T00:00:00+00:00</published>
        <updated>2015-06-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/projects/"/>
        <id>https://sgoel.dev/projects/</id>
        
        <summary type="html">&lt;p&gt;In my spare time, I tend to work on side projects for fun. This page lists the
ones which reached completion.&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>About</title>
        <published>2015-06-01T00:00:00+00:00</published>
        <updated>2015-06-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/about/"/>
        <id>https://sgoel.dev/about/</id>
        
        <summary type="html">&lt;p&gt;I&#x27;m a backend &amp;amp; infrastructure software developer based in the Munich area, Germany.&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Hello, World!</title>
        <published>2015-05-17T00:00:00+00:00</published>
        <updated>2015-05-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sgoel.dev/posts/hello-world/"/>
        <id>https://sgoel.dev/posts/hello-world/</id>
        
        <content type="html" xml:base="https://sgoel.dev/posts/hello-world/">&lt;p&gt;After a while without posting anything to the interwebs, I decided to start
writing again. I usually use Python, but this time I decided to go with
Jekyll, for no particular reason. I hope I&#x27;ll be more disciplined this time
around.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
