<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Python on blog.iankulin.com</title><link>https://blog.iankulin.com/tags/python/</link><description>Recent content in Python on blog.iankulin.com</description><generator>Hugo</generator><language>en-AU</language><lastBuildDate>Fri, 15 Sep 2023 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.iankulin.com/tags/python/index.xml" rel="self" type="application/rss+xml"/><item><title>Lightweight Web Servers</title><link>https://blog.iankulin.com/lightweight-web-servers/</link><pubDate>Fri, 15 Sep 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/lightweight-web-servers/</guid><description>&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-08-02-at-9.09.48-pm-2.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-02-at-9.09.48-pm-2.png" width="300" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been using the excellent &lt;a href="https://github.com/louislam/uptime-kuma"&gt;Uptime Kuma&lt;/a&gt; for my monitoring, but a couple of recent incidents - an external USB mount disappeared on a remote machine, an NVME drive filled up on a different node and stopped backups working because of a configuration error - have made me start to think about more robust monitoring.&lt;/p&gt;
&lt;p&gt;The are many great tools for this - &lt;a href="https://www.nagios.org/"&gt;Nagios&lt;/a&gt;, &lt;a href="https://prometheus.io/"&gt;Prometheus&lt;/a&gt; etc. but they are pretty substantial time investments for the excellent power. They can save time series data and display them beautifully. However, all I really want is to add some extra ability to Uptime Kuma.&lt;/p&gt;
&lt;p&gt;Uptime Kuma is already pretty great - it can parse a webpage to search for a particular phrase, it can execute searches in popular databases, it can ping, check a docker container is running and all sorts of other tricks - but it can&amp;rsquo;t check memory use of a service, or if a machine is running out of disk space. Uptime Kuma works in binary - things either pass a check, or they don&amp;rsquo;t. It does do some nice graphs of ping times, but that&amp;rsquo;s about all.&lt;/p&gt;
&lt;p&gt;I could expose some of this data - disk space free, CPU temp, checking a mount is working - pretty easily in a little Node endpoint. But it thinking about this, it made me wonder what the overhead of running Node (probably with Express) to carry out this menial task might be. I was thinking that the alternatives would be to use python/flask, or just to write it in C or Golang.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dev.to/wickdchromosome/is-the-pain-worth-the-gain-writing-webapps-in-c-benchmarks-vs-flask-and-nodejs-14l0"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-02-at-9.34.50-pm.png" width="129" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Whilst searching for answers about this, I found this excellent article from Bence Cotis. It turns out, that for very low loads (I&amp;rsquo;ll probably hit these endpoints once every five minutes) C is a bit better, but probably not (in my opinion) worth the hassle. I&amp;rsquo;ll stick to Node.&lt;/p&gt;</description></item><item><title>Beginning Python</title><link>https://blog.iankulin.com/beginning-python/</link><pubDate>Fri, 05 May 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/beginning-python/</guid><description>&lt;p&gt;I&amp;rsquo;ve gotten interested in &lt;a href="https://plaintextaccounting.org/"&gt;Plain Text Accounting&lt;/a&gt;, and I&amp;rsquo;m looking at using &lt;a href="https://beancount.github.io/"&gt;BeanCount&lt;/a&gt; for small business &amp;amp; personal finances. If you haven&amp;rsquo;t heard of this, it&amp;rsquo;s a command line program that uses text files with a human comprehensible syntax to define transactions, then acts on them to create the needed reports etc. A side benefit for developers is that it&amp;rsquo;s then easily version controlled using GIT, and of course there&amp;rsquo;s VS Code plugins for it.&lt;/p&gt;
&lt;p&gt;Transactions in one of these BeanCount text files looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-30-at-11.40.25-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;A good way to enter all these transactions is from your bank statement. Banks make this data available to customers, usually in a range of formats that are digestible by accounting programs. One of the formats I can get this data in looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;#34;116-002 123456789&amp;#34;,&amp;#34;05/07/2022&amp;#34;,&amp;#34;WDL05&amp;#34;,&amp;#34;-49.99&amp;#34;,&amp;#34;RETAIL PURCHASE&amp;#34;,&amp;#34;PAYPAL *EVERNOTE1, 4029357733&amp;#34;,&amp;#34;0107 AUD000000004999&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;#34;116-002 123456789&amp;#34;,&amp;#34;03/07/2022&amp;#34;,&amp;#34;WDL05&amp;#34;,&amp;#34;-95.34&amp;#34;,&amp;#34;RETAIL PURCHASE&amp;#34;,&amp;#34;PUMA ENERGY MOUN,MOUNT MELVILL&amp;#34;,&amp;#34;0207 AUD000000009534&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So the question is how do I get from one format to the other. BeanCount does have the hooks to enable you to write some Python to facilitate these imports, but I might want to do some extra processing - for example, the only thing I ever buy from Mt Melvile Puma is diesel for my ute, so that transaction can be written without any human intervention.&lt;/p&gt;
&lt;p&gt;So what I need is a command line program that takes the bank&amp;rsquo;s CSV input and spits out nicely formatted BeanCount format transaction text with as much of my work done for me as possible. I decided to do this in Python for a few reasons, but mostly because it&amp;rsquo;s the right tool for this sort of job, I might be able to contribute something back to BeanCount, and I should really learn the Python basics.&lt;/p&gt;
&lt;p&gt;The rest of this post looks at snippets of things I learned in the process. They might be helpful to someone completely new to Python but with some other programming experience.&lt;/p&gt;
&lt;h3 id="python-environment"&gt;Python Environment&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;m all in on &lt;a href="https://code.visualstudio.com/"&gt;VS Code&lt;/a&gt; these days, and I assumed Python was already installed on my Mac somewhere since BeanCount is in Python and I&amp;rsquo;d been using that all evening. Sure enough, when I created a .py file, VS Code suggested the Microsoft Plugin, then when I allowed that it prompted my to set up the environment, which I also agreed to. It found two versions of Python - a HomeBrew one and the BeanCount one which was a bit newer.&lt;/p&gt;
&lt;p&gt;Once I&amp;rsquo;d chosen that, it created a .venv, and I could type in Python code and execute it from VS Code, or drop into the terminal and run it at the command line. The whole operation was delightful.&lt;/p&gt;
&lt;h3 id="python"&gt;Python&lt;/h3&gt;
&lt;p&gt;Python is a high-level, general-purpose, dynamically typed language. It is interpreted and garbage collected - I sort of think of it as scripting, but with a real programming language - but that is majorly underselling it.&lt;/p&gt;
&lt;p&gt;Coming from Delphi/C++/Swift the use of indentation to define code blocks is slightly discomfiting, but no doubt I&amp;rsquo;ll get use to it. Python uses a combination of the colon and white space for blocks.&lt;/p&gt;
&lt;h3 id="command-line-arguments"&gt;Command Line Arguments&lt;/h3&gt;
&lt;p&gt;My plan for this utility is that I&amp;rsquo;ll type something like this at the command line to process the bank CSV file into a text file full of the BeanCount transactions:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python csv2bean.py bank.csv &amp;gt; 2022_July.bean
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So the first problem is grabbing the command line arguments. There&amp;rsquo;s a library to import for that - &lt;code&gt;sys&lt;/code&gt;. The arguments will be in an array &lt;code&gt;argv[]&lt;/code&gt;so the demo code could look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;import sys
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;print(&amp;#39;Self name: &amp;#39;, sys.argv[0])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;if len(sys.argv) &amp;gt; 1:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&amp;#39;First argument: &amp;#39;, sys.argv[1])
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-30-at-10.53.24-am.png" alt=""&gt;&lt;/p&gt;
&lt;h3 id="csv-processing"&gt;CSV Processing&lt;/h3&gt;
&lt;p&gt;There&amp;rsquo;s a couple of library options for processing CSV files. One is just called &lt;code&gt;csv&lt;/code&gt;, and the other &lt;code&gt;pandas&lt;/code&gt;. &lt;code&gt;pandas&lt;/code&gt; is a popular library that does all sorts of powerful stuff for data processing type tasks. For our simple needs, &lt;code&gt;[csv](https://docs.python.org/3/library/csv.html)&lt;/code&gt; seems fine.&lt;/p&gt;
&lt;p&gt;csv.reader(filename) will give us a collection of lines to iterate through, and we can break each line into a row array which will contain each piece of text.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# process a bank extended csv file to a beancount transaction
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;import csv, sys
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;if len(sys.argv) &amp;lt; 2:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&amp;#39;Error: supply a file name to process&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;input_file = sys.argv[1]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;with open(input_file, &amp;#39;r&amp;#39;) as csv_file:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; reader = csv.reader(csv_file)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; for row in reader:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if len(row) &amp;lt; 6:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&amp;#39;Error: expected more fields: &amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(row)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; trans_date = row[1]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; trans_type = row[2]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; trans_amount = float(row[3])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; trans_narration = row[4]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; trans_payee = row[5]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&amp;#34;On &amp;#34;+trans_date+ &amp;#34; &amp;#34;+str(trans_amount)+&amp;#34; paid to &amp;#34;+trans_payee+&amp;#34; for &amp;#34;+trans_narration)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; csv_file.close()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-04-30-at-11.18.03-am.png" alt=""&gt;&lt;/p&gt;
&lt;h3 id="python-functions"&gt;Python Functions&lt;/h3&gt;
&lt;p&gt;Beancount dates have to be in the format of YYYY-MM-DD and my Australian bank ones are currently DD/MM/YYYY so I need to do a little text processing. That seems like something that should be split off into a function.&lt;/p&gt;
&lt;p&gt;Functions are defined with &lt;code&gt;def&lt;/code&gt;, can accept arguments and return values with &lt;code&gt;return&lt;/code&gt;. Here&amp;rsquo;s a (non-functional) version of the function to format the date:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;def flip_date(dmy_date):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; return dmy_date
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can think of strings as arrays of characters, so the chopping and changing we want to do will end up like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;def flip_date(dmy_date):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if len(dmy_date) &amp;lt; 10:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&amp;#39;Error: date too short: &amp;#39;+dmy_date)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; return dmy_date[6:10]+&amp;#39;-&amp;#39;+dmy_date[3:5]+&amp;#39;-&amp;#39;+dmy_date[0:2]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The only notable thing there is that when we slice out a sub-string with these indexes, the second index is exclusive - ie it&amp;rsquo;s the position &lt;em&gt;after&lt;/em&gt; the last position we&amp;rsquo;re interested in.&lt;/p&gt;
&lt;h3 id="output"&gt;Output&lt;/h3&gt;
&lt;p&gt;That&amp;rsquo;s about all the work we need to do for this program, all that&amp;rsquo;s left is to spit it out in the correct format.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;print(trans_date+ &amp;#39; * &amp;#34;&amp;#39;+trans_payee+&amp;#39;&amp;#34; &amp;#34;&amp;#39;+trans_narration+&amp;#39;&amp;#34;&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;print(&amp;#39; Assets:Cheque: &amp;#39;+str(trans_amount)+&amp;#39; AUD&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;if trans_amount &amp;lt; 0.0:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&amp;#39; Expenses:GST_InputCredits: &amp;#39;+f&amp;#39;{-trans_amount/11:.2f}&amp;#39;+&amp;#39; AUD&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&amp;#39; Expenses:Unknown\n&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;else:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&amp;#39; Income:Unknown\n&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Most of that is self evident - string concatenation in Python is with the &lt;code&gt;+&lt;/code&gt; operator, and the &lt;code&gt;str()&lt;/code&gt; function does the type conversion from integers or floating point numbers.&lt;/p&gt;
&lt;p&gt;In Australia there&amp;rsquo;s a 10% GST (similar to VAT or sales tax), so the output code checks if this is a debit, and if so, calculates the GST by dividing the amount by 11. This is usually going to end in a number with a lot of decimal places, so it needs to the rounded to the nearest cent. That&amp;rsquo;s what this weird looking thing is doing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;f&amp;#39;{-trans_amount/11:.2f}&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s basically the &lt;code&gt;printf()&lt;/code&gt; type formats. The f at the start signifies that&amp;rsquo;s what&amp;rsquo;s happening, then the format goes after the colon.&lt;/p&gt;
&lt;h3 id="resources"&gt;Resources&lt;/h3&gt;
&lt;p&gt;Most everything I googled while figuring things out came up with useful results from either &lt;a href="https://www.w3schools.com/python/python_functions.asp"&gt;W3 School&lt;/a&gt;, or &lt;a href="https://www.digitalocean.com/community/tutorials/parse-csv-files-in-python"&gt;DigitalOcean&lt;/a&gt;, so I&amp;rsquo;d suggest checking them out for more.&lt;/p&gt;</description></item></channel></rss>