<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>/devices/pseudo/bitbucket@0,0:pseudo (Posts about Python)</title><link>https://www.jmcpdotcom.com/blog/</link><description></description><atom:link href="https://www.jmcpdotcom.com/blog/categories/python.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><copyright>Contents © 2022 &lt;a href="mailto:blogadmin@jmcpdotcom.com"&gt;jmcp&lt;/a&gt; </copyright><lastBuildDate>Thu, 21 Jul 2022 20:57:14 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Five letter words in the English language</title><link>https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/</link><dc:creator>jmcp</dc:creator><description>&lt;p&gt;Like many people I quite enjoy playing &lt;a class="reference external" href="https://www.nytimes.com/games/wordle/index.html"&gt;Wordle&lt;/a&gt;, and I quite enjoy playing &lt;a class="reference external" href="https://worldle.teuteuf.fr/"&gt;Worldle&lt;/a&gt;, too. I like both of these games so much that I've made completing them my Start Of Day (SOD) procedure.&lt;/p&gt;
&lt;p&gt;Yesterday's &lt;a class="reference external" href="https://worldle.teuteuf.fr/"&gt;Worldle&lt;/a&gt; was &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Vatican_City"&gt;Vatican City&lt;/a&gt;, a place that J and I visited when we had an amazing five week long trip to Europe in 2005. Worldle gives you a black-on-white image of the geographic area to guess, and each day's image is approximately the same size in your window.&lt;/p&gt;
&lt;p&gt;I thought this was pretty easy to guess - to me it just looks like a fortified place like a castle or ... a city state. There aren't too many of those on this planet so it was a 1 from 1 situation. I forgot to save a copy of the clue version, so imagine this picture below but with everything inside the walls filled in, in black:&lt;/p&gt;
&lt;img alt="Vatican City map from Wikimedia commons" src="https://www.jmcpdotcom.com/blog/images/2022/Vatican_City_map_EN.png" style="width: 600px;"&gt;
&lt;p&gt;This morning I got to thinking - if you've only got the edges of a word (start and end letters), how many unique combinations are there? [Yes, I'm only thinking about the English language].&lt;/p&gt;
&lt;p&gt;A quick check of &lt;cite&gt;/usr/share/dict/words&lt;/cite&gt; on my workstation (standard Linux dictionary installed) shows that out of 104334 words there are 7044 with five letters. Clarifying that just a little, if you remove those that are capitalised (proper nouns and acronyms) you're left with 4667 five letter words.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_0cdea51883d240869bcde650e20cbfa2-1" name="rest_code_0cdea51883d240869bcde650e20cbfa2-1" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_0cdea51883d240869bcde650e20cbfa2-1"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;
&lt;a id="rest_code_0cdea51883d240869bcde650e20cbfa2-2" name="rest_code_0cdea51883d240869bcde650e20cbfa2-2" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_0cdea51883d240869bcde650e20cbfa2-2"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"words"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"r"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readlines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a id="rest_code_0cdea51883d240869bcde650e20cbfa2-3" name="rest_code_0cdea51883d240869bcde650e20cbfa2-3" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_0cdea51883d240869bcde650e20cbfa2-3"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_0cdea51883d240869bcde650e20cbfa2-4" name="rest_code_0cdea51883d240869bcde650e20cbfa2-4" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_0cdea51883d240869bcde650e20cbfa2-4"&gt;&lt;/a&gt;&lt;span class="mi"&gt;104334&lt;/span&gt;
&lt;a id="rest_code_0cdea51883d240869bcde650e20cbfa2-5" name="rest_code_0cdea51883d240869bcde650e20cbfa2-5" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_0cdea51883d240869bcde650e20cbfa2-5"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fivers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_0cdea51883d240869bcde650e20cbfa2-6" name="rest_code_0cdea51883d240869bcde650e20cbfa2-6" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_0cdea51883d240869bcde650e20cbfa2-6"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fivers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_0cdea51883d240869bcde650e20cbfa2-7" name="rest_code_0cdea51883d240869bcde650e20cbfa2-7" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_0cdea51883d240869bcde650e20cbfa2-7"&gt;&lt;/a&gt;&lt;span class="mi"&gt;7044&lt;/span&gt;
&lt;a id="rest_code_0cdea51883d240869bcde650e20cbfa2-8" name="rest_code_0cdea51883d240869bcde650e20cbfa2-8" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_0cdea51883d240869bcde650e20cbfa2-8"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fivers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_0cdea51883d240869bcde650e20cbfa2-9" name="rest_code_0cdea51883d240869bcde650e20cbfa2-9" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_0cdea51883d240869bcde650e20cbfa2-9"&gt;&lt;/a&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ABC's"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"ABM's"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'AFAIK'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"AFC's"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"AMD's"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ANSIs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ANZUS'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"AOL's"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ASCII'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"ASL's"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ASPCA'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"ATM's"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"ATP's"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'AWACS'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"AWS's"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"AZT's"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Aaron'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Abbas'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Abdul'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Abe's"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_0cdea51883d240869bcde650e20cbfa2-10" name="rest_code_0cdea51883d240869bcde650e20cbfa2-10" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_0cdea51883d240869bcde650e20cbfa2-10"&gt;&lt;/a&gt;
&lt;a id="rest_code_0cdea51883d240869bcde650e20cbfa2-11" name="rest_code_0cdea51883d240869bcde650e20cbfa2-11" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_0cdea51883d240869bcde650e20cbfa2-11"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fivers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;a id="rest_code_0cdea51883d240869bcde650e20cbfa2-12" name="rest_code_0cdea51883d240869bcde650e20cbfa2-12" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_0cdea51883d240869bcde650e20cbfa2-12"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_0cdea51883d240869bcde650e20cbfa2-13" name="rest_code_0cdea51883d240869bcde650e20cbfa2-13" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_0cdea51883d240869bcde650e20cbfa2-13"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"^[a-z]&lt;/span&gt;&lt;span class="si"&gt;{5}&lt;/span&gt;&lt;span class="s2"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;a id="rest_code_0cdea51883d240869bcde650e20cbfa2-14" name="rest_code_0cdea51883d240869bcde650e20cbfa2-14" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_0cdea51883d240869bcde650e20cbfa2-14"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_0cdea51883d240869bcde650e20cbfa2-15" name="rest_code_0cdea51883d240869bcde650e20cbfa2-15" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_0cdea51883d240869bcde650e20cbfa2-15"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="n"&gt;fivers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_0cdea51883d240869bcde650e20cbfa2-16" name="rest_code_0cdea51883d240869bcde650e20cbfa2-16" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_0cdea51883d240869bcde650e20cbfa2-16"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;a id="rest_code_0cdea51883d240869bcde650e20cbfa2-17" name="rest_code_0cdea51883d240869bcde650e20cbfa2-17" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_0cdea51883d240869bcde650e20cbfa2-17"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fivers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_0cdea51883d240869bcde650e20cbfa2-18" name="rest_code_0cdea51883d240869bcde650e20cbfa2-18" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_0cdea51883d240869bcde650e20cbfa2-18"&gt;&lt;/a&gt;&lt;span class="mi"&gt;4667&lt;/span&gt;
&lt;a id="rest_code_0cdea51883d240869bcde650e20cbfa2-19" name="rest_code_0cdea51883d240869bcde650e20cbfa2-19" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_0cdea51883d240869bcde650e20cbfa2-19"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fivers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_0cdea51883d240869bcde650e20cbfa2-20" name="rest_code_0cdea51883d240869bcde650e20cbfa2-20" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_0cdea51883d240869bcde650e20cbfa2-20"&gt;&lt;/a&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'abaci'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'aback'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'abaft'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'abase'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'abash'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'abate'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'abbey'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'abbot'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'abeam'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'abets'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'abhor'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'abide'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'abler'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'abode'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'abort'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'about'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'above'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'abuse'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'abuts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'abuzz'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's good enough to look at the initial and final letter combinations. To do that we'll use a &lt;code class="docutils literal"&gt;set&lt;/code&gt;:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_9d16777482a2461da0314b536019e708-1" name="rest_code_9d16777482a2461da0314b536019e708-1" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_9d16777482a2461da0314b536019e708-1"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;enders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fivers&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_9d16777482a2461da0314b536019e708-2" name="rest_code_9d16777482a2461da0314b536019e708-2" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_9d16777482a2461da0314b536019e708-2"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_9d16777482a2461da0314b536019e708-3" name="rest_code_9d16777482a2461da0314b536019e708-3" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_9d16777482a2461da0314b536019e708-3"&gt;&lt;/a&gt;&lt;span class="mi"&gt;4667&lt;/span&gt;
&lt;a id="rest_code_9d16777482a2461da0314b536019e708-4" name="rest_code_9d16777482a2461da0314b536019e708-4" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_9d16777482a2461da0314b536019e708-4"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;snend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_9d16777482a2461da0314b536019e708-5" name="rest_code_9d16777482a2461da0314b536019e708-5" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_9d16777482a2461da0314b536019e708-5"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snend&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_9d16777482a2461da0314b536019e708-6" name="rest_code_9d16777482a2461da0314b536019e708-6" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_9d16777482a2461da0314b536019e708-6"&gt;&lt;/a&gt;&lt;span class="mi"&gt;411&lt;/span&gt;
&lt;a id="rest_code_9d16777482a2461da0314b536019e708-7" name="rest_code_9d16777482a2461da0314b536019e708-7" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_9d16777482a2461da0314b536019e708-7"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;snend&lt;/span&gt;
&lt;a id="rest_code_9d16777482a2461da0314b536019e708-8" name="rest_code_9d16777482a2461da0314b536019e708-8" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_9d16777482a2461da0314b536019e708-8"&gt;&lt;/a&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'v'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'j'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'j'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'v'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'v'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'j'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'v'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'j'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'v'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'j'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'j'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'j'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'v'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'j'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'j'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'v'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'v'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'j'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'j'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'v'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'v'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'v'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'j'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'v'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'v'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'v'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's quite a few pairs! I'm easily amused by things like this, so let's see how many words are in the list which start with 'g' and end with 'y':&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_6222f1a8097e4d3998c1805b27cfa966-1" name="rest_code_6222f1a8097e4d3998c1805b27cfa966-1" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_6222f1a8097e4d3998c1805b27cfa966-1"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;gy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fivers&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"g"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;a id="rest_code_6222f1a8097e4d3998c1805b27cfa966-2" name="rest_code_6222f1a8097e4d3998c1805b27cfa966-2" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_6222f1a8097e4d3998c1805b27cfa966-2"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;gy&lt;/span&gt;
&lt;a id="rest_code_6222f1a8097e4d3998c1805b27cfa966-3" name="rest_code_6222f1a8097e4d3998c1805b27cfa966-3" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_6222f1a8097e4d3998c1805b27cfa966-3"&gt;&lt;/a&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'gabby'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gaily'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gamey'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gassy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gaudy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gauzy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gawky'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gayly'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'geeky'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'giddy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gimpy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gipsy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'glory'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gluey'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'godly'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'golly'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'goody'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gooey'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'goofy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gouty'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gravy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'grimy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gully'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gummy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gunny'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'guppy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gushy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gusty'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gutsy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gypsy'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_6222f1a8097e4d3998c1805b27cfa966-4" name="rest_code_6222f1a8097e4d3998c1805b27cfa966-4" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_6222f1a8097e4d3998c1805b27cfa966-4"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_6222f1a8097e4d3998c1805b27cfa966-5" name="rest_code_6222f1a8097e4d3998c1805b27cfa966-5" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_6222f1a8097e4d3998c1805b27cfa966-5"&gt;&lt;/a&gt;&lt;span class="mi"&gt;30&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's check the distribution (aren't buckets fun?) amongst all the start/end combinations:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_6ddbb66673614b9a83a2843dd5a00ef7-1" name="rest_code_6ddbb66673614b9a83a2843dd5a00ef7-1" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_6ddbb66673614b9a83a2843dd5a00ef7-1"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;buckets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;a id="rest_code_6ddbb66673614b9a83a2843dd5a00ef7-2" name="rest_code_6ddbb66673614b9a83a2843dd5a00ef7-2" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_6ddbb66673614b9a83a2843dd5a00ef7-2"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;snend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_6ddbb66673614b9a83a2843dd5a00ef7-3" name="rest_code_6ddbb66673614b9a83a2843dd5a00ef7-3" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_6ddbb66673614b9a83a2843dd5a00ef7-3"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;buckets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fivers&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])])&lt;/span&gt;
&lt;a id="rest_code_6ddbb66673614b9a83a2843dd5a00ef7-4" name="rest_code_6ddbb66673614b9a83a2843dd5a00ef7-4" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_6ddbb66673614b9a83a2843dd5a00ef7-4"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I admit some surprise at seeing that there are 92 start/end combinations which only have one word in the list&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_227be395bb5c4844a4e5f4ea0231314c-1" name="rest_code_227be395bb5c4844a4e5f4ea0231314c-1" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_227be395bb5c4844a4e5f4ea0231314c-1"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;unobuckets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;buckets&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;buckets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_227be395bb5c4844a4e5f4ea0231314c-2" name="rest_code_227be395bb5c4844a4e5f4ea0231314c-2" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_227be395bb5c4844a4e5f4ea0231314c-2"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unobuckets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_227be395bb5c4844a4e5f4ea0231314c-3" name="rest_code_227be395bb5c4844a4e5f4ea0231314c-3" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_227be395bb5c4844a4e5f4ea0231314c-3"&gt;&lt;/a&gt;&lt;span class="mi"&gt;92&lt;/span&gt;
&lt;a id="rest_code_227be395bb5c4844a4e5f4ea0231314c-4" name="rest_code_227be395bb5c4844a4e5f4ea0231314c-4" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_227be395bb5c4844a4e5f4ea0231314c-4"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;unobuckets&lt;/span&gt;
&lt;a id="rest_code_227be395bb5c4844a4e5f4ea0231314c-5" name="rest_code_227be395bb5c4844a4e5f4ea0231314c-5" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_227be395bb5c4844a4e5f4ea0231314c-5"&gt;&lt;/a&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'j'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'v'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'j'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'v'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'j'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'v'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'j'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'j'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'v'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's choose five:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code python"&gt;&lt;a id="rest_code_f2c976fea81b4580a9b626054d484f74-1" name="rest_code_f2c976fea81b4580a9b626054d484f74-1" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_f2c976fea81b4580a9b626054d484f74-1"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"z"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"f"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"z"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"q"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"n"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"r"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_f2c976fea81b4580a9b626054d484f74-2" name="rest_code_f2c976fea81b4580a9b626054d484f74-2" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_f2c976fea81b4580a9b626054d484f74-2"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fivers&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt;
&lt;a id="rest_code_f2c976fea81b4580a9b626054d484f74-3" name="rest_code_f2c976fea81b4580a9b626054d484f74-3" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_f2c976fea81b4580a9b626054d484f74-3"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_f2c976fea81b4580a9b626054d484f74-4" name="rest_code_f2c976fea81b4580a9b626054d484f74-4" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_f2c976fea81b4580a9b626054d484f74-4"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;a id="rest_code_f2c976fea81b4580a9b626054d484f74-5" name="rest_code_f2c976fea81b4580a9b626054d484f74-5" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_f2c976fea81b4580a9b626054d484f74-5"&gt;&lt;/a&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'abuzz'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_f2c976fea81b4580a9b626054d484f74-6" name="rest_code_f2c976fea81b4580a9b626054d484f74-6" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_f2c976fea81b4580a9b626054d484f74-6"&gt;&lt;/a&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'frizz'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_f2c976fea81b4580a9b626054d484f74-7" name="rest_code_f2c976fea81b4580a9b626054d484f74-7" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_f2c976fea81b4580a9b626054d484f74-7"&gt;&lt;/a&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'queen'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_f2c976fea81b4580a9b626054d484f74-8" name="rest_code_f2c976fea81b4580a9b626054d484f74-8" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_f2c976fea81b4580a9b626054d484f74-8"&gt;&lt;/a&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'relax'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_f2c976fea81b4580a9b626054d484f74-9" name="rest_code_f2c976fea81b4580a9b626054d484f74-9" href="https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/#rest_code_f2c976fea81b4580a9b626054d484f74-9"&gt;&lt;/a&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'detox'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I didn't really have a point to make here, I just wanted to share my amusement at how many five letter words there are to guess compared to the approximately 300 geographic entities on the planet that you'll shown the edges of in &lt;a class="reference external" href="https://worldle.teuteuf.fr/"&gt;Worldle&lt;/a&gt;. Also that knowing &lt;a class="reference external" href="https://www.python.org"&gt;Python&lt;/a&gt; means you can make short work of asking and answering these questions.&lt;/p&gt;
&lt;!-- put references after this point --&gt;</description><category>amusements</category><category>language</category><category>Python</category><category>Wordle</category><category>Worldle</category><guid>https://www.jmcpdotcom.com/blog/posts/2022-07-21-five-letter-words/</guid><pubDate>Thu, 21 Jul 2022 02:37:00 GMT</pubDate></item><item><title>Some Pythonic Kafka stuff</title><link>https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/</link><dc:creator>jmcp</dc:creator><description>&lt;p&gt;I've been actively interviewing over the last few months, and was recently talking with a cloud streaming
provider about a Staff Engineer SRE role specializing in &lt;a class="reference external" href="https://kafka.apache.org"&gt;Apache Kafka&lt;/a&gt;. I've been doing a lot of work
in that space for $employer and quite like it as a data streaming solution.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The interviews went well, and they sent me a do-at-home challenge with a one week timeout.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The gist of it was I had to create a &lt;a class="reference external" href="https://flask.palletsprojects.com"&gt;Flask&lt;/a&gt; app which allowed the user to enter a URL to monitor for status
on a given frequency, use &lt;a class="reference external" href="https://www.python.org"&gt;Python&lt;/a&gt; to write a &lt;a class="reference external" href="https://kafka.apache.org/documentation/#producerapi"&gt;Kafka Producer&lt;/a&gt; to publish this data to a topic, and
write a &lt;a class="reference external" href="https://kafka.apache.org/documentation/#consumerapi"&gt;Kafka Consumer&lt;/a&gt; to read from the topic and insert into a &lt;a class="reference external" href="https://www.postgresql.org"&gt;PostgreSQL&lt;/a&gt; database.&lt;/p&gt;
&lt;p&gt;Fun!&lt;/p&gt;
&lt;p&gt;I did a brief investigation into using threads within a &lt;a class="reference external" href="https://flask.palletsprojects.com"&gt;Flask&lt;/a&gt; app for the monitoring code, but quickly
decided that a better architecture would be to do the monitoring via a separate daemon. Separation of
concerns to allow for easier maintenance. Suddenly I'm all about ongoing maintenance rather then how
quickly I can deliver a new feature... Hmmm.&lt;/p&gt;
&lt;p&gt;The next step was to sketch out the table schema I wanted in the database:&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br&gt;&lt;/div&gt;
&lt;div class="line"&gt;&lt;br&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class="code sql"&gt;&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-1" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;SEQUENCE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urltargets_seq&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-2" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-2"&gt;&lt;/a&gt;            &lt;span class="k"&gt;INCREMENT&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-3" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-3"&gt;&lt;/a&gt;            &lt;span class="k"&gt;MINVALUE&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-4" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-4"&gt;&lt;/a&gt;            &lt;span class="k"&gt;MAXVALUE&lt;/span&gt; &lt;span class="mi"&gt;9223372036854775807&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-5" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-5"&gt;&lt;/a&gt;            &lt;span class="k"&gt;START&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-6" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-6"&gt;&lt;/a&gt;            &lt;span class="k"&gt;CACHE&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-7" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-7"&gt;&lt;/a&gt;            &lt;span class="k"&gt;NO&lt;/span&gt; &lt;span class="k"&gt;CYCLE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-8" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-8"&gt;&lt;/a&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-9" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-9"&gt;&lt;/a&gt;    &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;SEQUENCE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;monitor_results_seq&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-10" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-10"&gt;&lt;/a&gt;            &lt;span class="k"&gt;INCREMENT&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-11" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-11"&gt;&lt;/a&gt;            &lt;span class="k"&gt;MINVALUE&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-12" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-12"&gt;&lt;/a&gt;            &lt;span class="k"&gt;MAXVALUE&lt;/span&gt; &lt;span class="mi"&gt;9223372036854775807&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-13" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-13"&gt;&lt;/a&gt;            &lt;span class="k"&gt;START&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-14" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-14"&gt;&lt;/a&gt;            &lt;span class="k"&gt;CACHE&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-15" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-15"&gt;&lt;/a&gt;            &lt;span class="k"&gt;NO&lt;/span&gt; &lt;span class="k"&gt;CYCLE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-16" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-16"&gt;&lt;/a&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-17" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-17"&gt;&lt;/a&gt;    &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;urltargets&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-18" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-18"&gt;&lt;/a&gt;            &lt;span class="n"&gt;urltargets_pk&lt;/span&gt;           &lt;span class="n"&gt;int4&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;nextval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'urltargets_seq'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;regclass&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-19" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-19"&gt;&lt;/a&gt;            &lt;span class="n"&gt;urltarget&lt;/span&gt;                       &lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-20" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-20"&gt;&lt;/a&gt;            &lt;span class="n"&gt;monitor_frequency&lt;/span&gt;       &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;monitor_frequency&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-21" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-21"&gt;&lt;/a&gt;            &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;urltargets_pkey&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urltargets_pk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-22" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-22"&gt;&lt;/a&gt;    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-23" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-23"&gt;&lt;/a&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-24" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-24"&gt;&lt;/a&gt;    &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;monitor_results&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-25" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-25"&gt;&lt;/a&gt;            &lt;span class="n"&gt;monitor_results_pk&lt;/span&gt;      &lt;span class="n"&gt;int4&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;nextval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'monitor_results_seq'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;regclass&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-26" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-26"&gt;&lt;/a&gt;            &lt;span class="n"&gt;http_status&lt;/span&gt;                     &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-27" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-27"&gt;&lt;/a&gt;            &lt;span class="n"&gt;start_time&lt;/span&gt;                      &lt;span class="k"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="k"&gt;time&lt;/span&gt; &lt;span class="k"&gt;zone&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-28" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-28"&gt;&lt;/a&gt;            &lt;span class="n"&gt;duration&lt;/span&gt;            &lt;span class="n"&gt;int4&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-29" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-29"&gt;&lt;/a&gt;            &lt;span class="n"&gt;urltarget_fk&lt;/span&gt;            &lt;span class="n"&gt;int4&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-30" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-30"&gt;&lt;/a&gt;            &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;monitor_results_fk_fkey&lt;/span&gt; &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urltarget_fk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;urltargets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urltargets_pk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_1f6d4d21b7e649b8b7002355d12febd9-31" name="rest_code_1f6d4d21b7e649b8b7002355d12febd9-31"&gt;&lt;/a&gt;    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br&gt;&lt;/div&gt;
&lt;div class="line"&gt;&lt;br&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Having decided that I would offer monitoring frequencies of 1, 2, 5, 10, 20 and 30 minutes, I created views
for the &lt;a class="reference external" href="https://flask.palletsprojects.com"&gt;Flask&lt;/a&gt; app to use as well, rather than direct queries. They all look like this, with other values
substituted in as you would expect.&lt;/p&gt;
&lt;pre class="code sql"&gt;&lt;a id="rest_code_41e75a1f12df42c481b715277890a5d1-1" name="rest_code_41e75a1f12df42c481b715277890a5d1-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;view_1m&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt;
&lt;a id="rest_code_41e75a1f12df42c481b715277890a5d1-2" name="rest_code_41e75a1f12df42c481b715277890a5d1-2"&gt;&lt;/a&gt;            &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;mr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;monitor_results_pk&lt;/span&gt;
&lt;a id="rest_code_41e75a1f12df42c481b715277890a5d1-3" name="rest_code_41e75a1f12df42c481b715277890a5d1-3"&gt;&lt;/a&gt;                    &lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;ut&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urltarget&lt;/span&gt;
&lt;a id="rest_code_41e75a1f12df42c481b715277890a5d1-4" name="rest_code_41e75a1f12df42c481b715277890a5d1-4"&gt;&lt;/a&gt;                    &lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;mr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http_status&lt;/span&gt;
&lt;a id="rest_code_41e75a1f12df42c481b715277890a5d1-5" name="rest_code_41e75a1f12df42c481b715277890a5d1-5"&gt;&lt;/a&gt;                    &lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;mr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_time&lt;/span&gt;
&lt;a id="rest_code_41e75a1f12df42c481b715277890a5d1-6" name="rest_code_41e75a1f12df42c481b715277890a5d1-6"&gt;&lt;/a&gt;                    &lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;mr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;
&lt;a id="rest_code_41e75a1f12df42c481b715277890a5d1-7" name="rest_code_41e75a1f12df42c481b715277890a5d1-7"&gt;&lt;/a&gt;            &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;monitor_results&lt;/span&gt; &lt;span class="n"&gt;mr&lt;/span&gt;
&lt;a id="rest_code_41e75a1f12df42c481b715277890a5d1-8" name="rest_code_41e75a1f12df42c481b715277890a5d1-8"&gt;&lt;/a&gt;            &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;urltargets&lt;/span&gt; &lt;span class="n"&gt;ut&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;ut&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urltargets_pk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urltarget_fk&lt;/span&gt;
&lt;a id="rest_code_41e75a1f12df42c481b715277890a5d1-9" name="rest_code_41e75a1f12df42c481b715277890a5d1-9"&gt;&lt;/a&gt;            &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ut&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;monitor_frequency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;a id="rest_code_41e75a1f12df42c481b715277890a5d1-10" name="rest_code_41e75a1f12df42c481b715277890a5d1-10"&gt;&lt;/a&gt;    &lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br&gt;&lt;/div&gt;
&lt;div class="line"&gt;&lt;br&gt;&lt;/div&gt;
&lt;div class="line"&gt;&lt;br&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Since I really like seeing schemata visualised, I created a nice(ish) ERD as well:&lt;/p&gt;
&lt;img alt="/images/2021/DB-ERD.png" src="https://www.jmcpdotcom.com/blog/images/2021/DB-ERD.png"&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Well that was straight forward, how about the application and daemon?&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I split out the setup functionality into a separate file importable by the &lt;a class="reference external" href="https://flask.palletsprojects.com"&gt;Flask&lt;/a&gt; app,
the monitor daemon and the consumer. This contained the database connection, &lt;a class="reference external" href="https://kafka.apache.org/documentation/#producerapi"&gt;Kafka Producer&lt;/a&gt;
and &lt;a class="reference external" href="https://kafka.apache.org/documentation/#consumerapi"&gt;Kafka Consumer&lt;/a&gt; code. There's an interesting little niggle in the &lt;a class="reference external" href="https://kafka.apache.org/documentation/#producerapi"&gt;Kafka Producer&lt;/a&gt;
setup which is not immediately obvious and required a bit of digging in &lt;a class="reference external" href="https://stackoverflow.com"&gt;StackOverflow&lt;/a&gt;
as well as enabling debug output with &lt;a class="reference external" href="https://github.com/edenhill/librdkafka/"&gt;librdkafka&lt;/a&gt;:&lt;/p&gt;
&lt;div class="code"&gt;&lt;table class="codetable"&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-1"&gt;&lt;code data-line-number=" 1"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-1" name="rest_code_2850825581f447efa60afe7274348cc8-1"&gt;&lt;/a&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_get_kafka_configuration&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-2"&gt;&lt;code data-line-number=" 2"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-2" name="rest_code_2850825581f447efa60afe7274348cc8-2"&gt;&lt;/a&gt;      &lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-3"&gt;&lt;code data-line-number=" 3"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-3" name="rest_code_2850825581f447efa60afe7274348cc8-3"&gt;&lt;/a&gt;&lt;span class="sd"&gt;      Common function to retrieve the Kafka configuration.&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-4"&gt;&lt;code data-line-number=" 4"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-4" name="rest_code_2850825581f447efa60afe7274348cc8-4"&gt;&lt;/a&gt;&lt;span class="sd"&gt;      """&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-5"&gt;&lt;code data-line-number=" 5"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-5" name="rest_code_2850825581f447efa60afe7274348cc8-5"&gt;&lt;/a&gt;      &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;appconfig&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-6"&gt;&lt;code data-line-number=" 6"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-6" name="rest_code_2850825581f447efa60afe7274348cc8-6"&gt;&lt;/a&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-7"&gt;&lt;code data-line-number=" 7"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-7" name="rest_code_2850825581f447efa60afe7274348cc8-7"&gt;&lt;/a&gt;      &lt;span class="n"&gt;configuration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-8"&gt;&lt;code data-line-number=" 8"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-8" name="rest_code_2850825581f447efa60afe7274348cc8-8"&gt;&lt;/a&gt;          &lt;span class="s2"&gt;"bootstrap.servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;appconfig&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"kafka"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"broker"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-9"&gt;&lt;code data-line-number=" 9"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-9" name="rest_code_2850825581f447efa60afe7274348cc8-9"&gt;&lt;/a&gt;          &lt;span class="s2"&gt;"group.id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"website-monitor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-10"&gt;&lt;code data-line-number="10"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-10" name="rest_code_2850825581f447efa60afe7274348cc8-10"&gt;&lt;/a&gt;          &lt;span class="s2"&gt;"ssl.key.location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;appconfig&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"kafka"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"keyfile"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-11"&gt;&lt;code data-line-number="11"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-11" name="rest_code_2850825581f447efa60afe7274348cc8-11"&gt;&lt;/a&gt;          &lt;span class="s2"&gt;"ssl.certificate.location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;appconfig&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"kafka"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"certfile"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-12"&gt;&lt;code data-line-number="12"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-12" name="rest_code_2850825581f447efa60afe7274348cc8-12"&gt;&lt;/a&gt;          &lt;span class="s2"&gt;"ssl.ca.location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;appconfig&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"kafka"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"cafile"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-13"&gt;&lt;code data-line-number="13"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-13" name="rest_code_2850825581f447efa60afe7274348cc8-13"&gt;&lt;/a&gt;          &lt;span class="s2"&gt;"security.protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"SSL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-14"&gt;&lt;code data-line-number="14"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-14" name="rest_code_2850825581f447efa60afe7274348cc8-14"&gt;&lt;/a&gt;          &lt;span class="c1"&gt;# "debug": "eos, broker, admin",  # re-enable if needed&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-15"&gt;&lt;code data-line-number="15"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-15" name="rest_code_2850825581f447efa60afe7274348cc8-15"&gt;&lt;/a&gt;          &lt;span class="s1"&gt;'transaction.timeout.ms'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-16"&gt;&lt;code data-line-number="16"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-16" name="rest_code_2850825581f447efa60afe7274348cc8-16"&gt;&lt;/a&gt;          &lt;span class="s1"&gt;'enable.idempotence'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-17"&gt;&lt;code data-line-number="17"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-17" name="rest_code_2850825581f447efa60afe7274348cc8-17"&gt;&lt;/a&gt;      &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-18"&gt;&lt;code data-line-number="18"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-18" name="rest_code_2850825581f447efa60afe7274348cc8-18"&gt;&lt;/a&gt;      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-19"&gt;&lt;code data-line-number="19"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-19" name="rest_code_2850825581f447efa60afe7274348cc8-19"&gt;&lt;/a&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-20"&gt;&lt;code data-line-number="20"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-20" name="rest_code_2850825581f447efa60afe7274348cc8-20"&gt;&lt;/a&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-21"&gt;&lt;code data-line-number="21"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-21" name="rest_code_2850825581f447efa60afe7274348cc8-21"&gt;&lt;/a&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setup_kafka_producer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-22"&gt;&lt;code data-line-number="22"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-22" name="rest_code_2850825581f447efa60afe7274348cc8-22"&gt;&lt;/a&gt;      &lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-23"&gt;&lt;code data-line-number="23"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-23" name="rest_code_2850825581f447efa60afe7274348cc8-23"&gt;&lt;/a&gt;&lt;span class="sd"&gt;      Creates the connection to our Kafka brokers so we can publish&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-24"&gt;&lt;code data-line-number="24"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-24" name="rest_code_2850825581f447efa60afe7274348cc8-24"&gt;&lt;/a&gt;&lt;span class="sd"&gt;      messages to the topics we want. We take the {view} argument so&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-25"&gt;&lt;code data-line-number="25"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-25" name="rest_code_2850825581f447efa60afe7274348cc8-25"&gt;&lt;/a&gt;&lt;span class="sd"&gt;      that we can avoid blatting multiple producers together and getting&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-26"&gt;&lt;code data-line-number="26"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-26" name="rest_code_2850825581f447efa60afe7274348cc8-26"&gt;&lt;/a&gt;&lt;span class="sd"&gt;      errors from the broker about zombies and fencing. See&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-27"&gt;&lt;code data-line-number="27"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-27" name="rest_code_2850825581f447efa60afe7274348cc8-27"&gt;&lt;/a&gt;&lt;span class="sd"&gt;      https://stackoverflow.com/questions/50335227/how-to-pick-a-kafka-transaction-id&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-28"&gt;&lt;code data-line-number="28"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-28" name="rest_code_2850825581f447efa60afe7274348cc8-28"&gt;&lt;/a&gt;&lt;span class="sd"&gt;      for more details&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-29"&gt;&lt;code data-line-number="29"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-29" name="rest_code_2850825581f447efa60afe7274348cc8-29"&gt;&lt;/a&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-30"&gt;&lt;code data-line-number="30"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-30" name="rest_code_2850825581f447efa60afe7274348cc8-30"&gt;&lt;/a&gt;&lt;span class="sd"&gt;      Return: a Producer&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-31"&gt;&lt;code data-line-number="31"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-31" name="rest_code_2850825581f447efa60afe7274348cc8-31"&gt;&lt;/a&gt;&lt;span class="sd"&gt;      """&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-32"&gt;&lt;code data-line-number="32"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-32" name="rest_code_2850825581f447efa60afe7274348cc8-32"&gt;&lt;/a&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-33"&gt;&lt;code data-line-number="33"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-33" name="rest_code_2850825581f447efa60afe7274348cc8-33"&gt;&lt;/a&gt;      &lt;span class="n"&gt;configuration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_get_kafka_configuration&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-34"&gt;&lt;code data-line-number="34"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-34" name="rest_code_2850825581f447efa60afe7274348cc8-34"&gt;&lt;/a&gt;      &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"transactional.id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"website-monitor"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-35"&gt;&lt;code data-line-number="35"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-35" name="rest_code_2850825581f447efa60afe7274348cc8-35"&gt;&lt;/a&gt;      &lt;span class="n"&gt;kafkaProducer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Producer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-36"&gt;&lt;code data-line-number="36"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-36" name="rest_code_2850825581f447efa60afe7274348cc8-36"&gt;&lt;/a&gt;      &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-37"&gt;&lt;code data-line-number="37"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-37" name="rest_code_2850825581f447efa60afe7274348cc8-37"&gt;&lt;/a&gt;          &lt;span class="n"&gt;kafkaProducer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init_transactions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-38"&gt;&lt;code data-line-number="38"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-38" name="rest_code_2850825581f447efa60afe7274348cc8-38"&gt;&lt;/a&gt;      &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;KafkaError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ke&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-39"&gt;&lt;code data-line-number="39"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-39" name="rest_code_2850825581f447efa60afe7274348cc8-39"&gt;&lt;/a&gt;          &lt;span class="c1"&gt;# If we can't do this, then we have to quit&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-40"&gt;&lt;code data-line-number="40"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-40" name="rest_code_2850825581f447efa60afe7274348cc8-40"&gt;&lt;/a&gt;          &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"""Producer failed to init_transactions(), throwing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ke&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-41"&gt;&lt;code data-line-number="41"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-41" name="rest_code_2850825581f447efa60afe7274348cc8-41"&gt;&lt;/a&gt;          &lt;span class="k"&gt;raise&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-42"&gt;&lt;code data-line-number="42"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-42" name="rest_code_2850825581f447efa60afe7274348cc8-42"&gt;&lt;/a&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2850825581f447efa60afe7274348cc8-43"&gt;&lt;code data-line-number="43"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2850825581f447efa60afe7274348cc8-43" name="rest_code_2850825581f447efa60afe7274348cc8-43"&gt;&lt;/a&gt;      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;kafkaProducer&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br&gt;&lt;/div&gt;
&lt;div class="line"&gt;&lt;br&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;When I was working with the daemon, my first iteration tried opening the DB connection
and Producer in each thread's (one for each frequency) &lt;code class="docutils literal"&gt;__init__()&lt;/code&gt; function, and .... that didn't work.&lt;/p&gt;
&lt;p&gt;The DB connection is not &lt;a class="reference external" href="https://docs.python.org/3/library/pickle.html"&gt;picklable&lt;/a&gt;, so does _not_ survive the call to &lt;code class="docutils literal"&gt;os.fork()&lt;/code&gt;. Once I had rewritten the
setup and run methods to get the DB connection, that part was groovy.&lt;/p&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://kafka.apache.org/documentation/#producerapi"&gt;Kafka Producer&lt;/a&gt; still required a bit of work. After reading through stackoverflow and the upstream for
&lt;a class="reference external" href="https://github.com/edenhill/librdkafka/"&gt;librdkafka&lt;/a&gt;, I saw that I needed to similarly delay initialising the producer until the thread's
&lt;code class="docutils literal"&gt;run()&lt;/code&gt; method was called. I also observed that each Producer should also initialise the transaction feature,
but leave the &lt;code class="docutils literal"&gt;begin&lt;/code&gt;... &lt;code class="docutils literal"&gt;end&lt;/code&gt; of the transaction to when it was called to publish a message.&lt;/p&gt;
&lt;p&gt;I still had a problem, though - &lt;em&gt;some&lt;/em&gt; transactions would get through, but then the Producer would be
&lt;strong&gt;fenced&lt;/strong&gt;. This was the &lt;em&gt;niggle&lt;/em&gt;, and where the &lt;a class="reference external" href="https://stackoverflow.com"&gt;StackOverflow&lt;/a&gt; comments helped me out:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Finally, in distributed environments, applications will crash or —worse!— temporarily
lose connectivity to the rest of the system. Typically, new instances are automatically
started to replace the ones which were deemed lost. Through this process, we may have
multiple instances processing the same input topics and writing to the same output
topics, causing duplicate outputs and violating the exactly once processing semantics.&lt;/p&gt;
&lt;p&gt;We call this the problem of “zombie instances.” [emphasis added]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I realised that I was giving the same transactional id to each of the six producer instances (setting
the 'transactional.id' in the configuration dict generated by &lt;code class="docutils literal"&gt;_get_kafka_configuration()&lt;/code&gt;, so I needed
to uniqify them somehow. I decided to pass the monitoring frequency of the thread to the setup function,
and ... booyah, I had messages being published.&lt;/p&gt;
&lt;p&gt;That was a really nice feeling.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;There is one other aspect of the monitoring daemon that I need to mention. Since each thread reads its
list of URLs to monitor each time it wakes, I wanted to parallelize this effort. Monitoring each of
the URLs in series could easily take too long from a &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;sleep(...)&lt;/span&gt;&lt;/code&gt; point of view, and I &lt;em&gt;really&lt;/em&gt; did not
want to just fork a whole heap of processes and thread either - avoiding the potential for a &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Fork_bomb"&gt;fork-bomb&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To work around this I used the &lt;a class="reference external" href="https://www.python.org"&gt;Python&lt;/a&gt; standard library &lt;a class="reference external" href="https://docs.python.org/3/library/concurrent.futures.html"&gt;concurrent.futures&lt;/a&gt; with a ThreadPoolExecutor
for each target URL. Adding attributes to the &lt;code class="docutils literal"&gt;future&lt;/code&gt; object enabled me to use an &lt;code class="docutils literal"&gt;add_done_callback&lt;/code&gt;
so that when the future crystallized it would then publish the message.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="code"&gt;&lt;table class="codetable"&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-1"&gt;&lt;code data-line-number=" 1"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-1" name="rest_code_2d5e58a399814a09a408127e5643cfde-1"&gt;&lt;/a&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-2"&gt;&lt;code data-line-number=" 2"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-2" name="rest_code_2d5e58a399814a09a408127e5643cfde-2"&gt;&lt;/a&gt;      &lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-3"&gt;&lt;code data-line-number=" 3"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-3" name="rest_code_2d5e58a399814a09a408127e5643cfde-3"&gt;&lt;/a&gt;&lt;span class="sd"&gt;      Runs the monitor, updates account-keeping and kicks off&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-4"&gt;&lt;code data-line-number=" 4"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-4" name="rest_code_2d5e58a399814a09a408127e5643cfde-4"&gt;&lt;/a&gt;&lt;span class="sd"&gt;      notifications if required. Then back to sleep.&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-5"&gt;&lt;code data-line-number=" 5"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-5" name="rest_code_2d5e58a399814a09a408127e5643cfde-5"&gt;&lt;/a&gt;&lt;span class="sd"&gt;      """&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-6"&gt;&lt;code data-line-number=" 6"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-6" name="rest_code_2d5e58a399814a09a408127e5643cfde-6"&gt;&lt;/a&gt;      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_producer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;setup_kafka_producer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_view&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-7"&gt;&lt;code data-line-number=" 7"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-7" name="rest_code_2d5e58a399814a09a408127e5643cfde-7"&gt;&lt;/a&gt;      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;setup_db&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-8"&gt;&lt;code data-line-number=" 8"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-8" name="rest_code_2d5e58a399814a09a408127e5643cfde-8"&gt;&lt;/a&gt;      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-9"&gt;&lt;code data-line-number=" 9"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-9" name="rest_code_2d5e58a399814a09a408127e5643cfde-9"&gt;&lt;/a&gt;      &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-10"&gt;&lt;code data-line-number="10"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-10" name="rest_code_2d5e58a399814a09a408127e5643cfde-10"&gt;&lt;/a&gt;          &lt;span class="n"&gt;alltargets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_get_targets&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-11"&gt;&lt;code data-line-number="11"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-11" name="rest_code_2d5e58a399814a09a408127e5643cfde-11"&gt;&lt;/a&gt;          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;alltargets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-12"&gt;&lt;code data-line-number="12"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-12" name="rest_code_2d5e58a399814a09a408127e5643cfde-12"&gt;&lt;/a&gt;              &lt;span class="c1"&gt;# We use a 'with' statement to ensure threads in the pool&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-13"&gt;&lt;code data-line-number="13"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-13" name="rest_code_2d5e58a399814a09a408127e5643cfde-13"&gt;&lt;/a&gt;              &lt;span class="c1"&gt;# are cleaned up promptly&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-14"&gt;&lt;code data-line-number="14"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-14" name="rest_code_2d5e58a399814a09a408127e5643cfde-14"&gt;&lt;/a&gt;              &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;cf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-15"&gt;&lt;code data-line-number="15"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-15" name="rest_code_2d5e58a399814a09a408127e5643cfde-15"&gt;&lt;/a&gt;                  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tgt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;alltargets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-16"&gt;&lt;code data-line-number="16"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-16" name="rest_code_2d5e58a399814a09a408127e5643cfde-16"&gt;&lt;/a&gt;                      &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;check_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-17"&gt;&lt;code data-line-number="17"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-17" name="rest_code_2d5e58a399814a09a408127e5643cfde-17"&gt;&lt;/a&gt;                                               &lt;span class="n"&gt;tgt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;tgt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-18"&gt;&lt;code data-line-number="18"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-18" name="rest_code_2d5e58a399814a09a408127e5643cfde-18"&gt;&lt;/a&gt;                      &lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tgt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;tgt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-19"&gt;&lt;code data-line-number="19"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-19" name="rest_code_2d5e58a399814a09a408127e5643cfde-19"&gt;&lt;/a&gt;                      &lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;producer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_producer&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-20"&gt;&lt;code data-line-number="20"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-20" name="rest_code_2d5e58a399814a09a408127e5643cfde-20"&gt;&lt;/a&gt;                      &lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_done_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;construct_and_publish&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-21"&gt;&lt;code data-line-number="21"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-21" name="rest_code_2d5e58a399814a09a408127e5643cfde-21"&gt;&lt;/a&gt;                      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_futures&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-22"&gt;&lt;code data-line-number="22"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-22" name="rest_code_2d5e58a399814a09a408127e5643cfde-22"&gt;&lt;/a&gt;                  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_completed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_futures&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-23"&gt;&lt;code data-line-number="23"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-23" name="rest_code_2d5e58a399814a09a408127e5643cfde-23"&gt;&lt;/a&gt;                      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-24"&gt;&lt;code data-line-number="24"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-24" name="rest_code_2d5e58a399814a09a408127e5643cfde-24"&gt;&lt;/a&gt;                          &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_futures&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-25"&gt;&lt;code data-line-number="25"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-25" name="rest_code_2d5e58a399814a09a408127e5643cfde-25"&gt;&lt;/a&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_2d5e58a399814a09a408127e5643cfde-26"&gt;&lt;code data-line-number="26"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_2d5e58a399814a09a408127e5643cfde-26" name="rest_code_2d5e58a399814a09a408127e5643cfde-26"&gt;&lt;/a&gt;          &lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_sleeptime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The check and publish methods are outside of the thread definition:&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="code"&gt;&lt;table class="codetable"&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-1"&gt;&lt;code data-line-number=" 1"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-1" name="rest_code_03c5c44fafec4c178433057abbcd8db4-1"&gt;&lt;/a&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;construct_and_publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-2"&gt;&lt;code data-line-number=" 2"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-2" name="rest_code_03c5c44fafec4c178433057abbcd8db4-2"&gt;&lt;/a&gt;      &lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-3"&gt;&lt;code data-line-number=" 3"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-3" name="rest_code_03c5c44fafec4c178433057abbcd8db4-3"&gt;&lt;/a&gt;&lt;span class="sd"&gt;      Callback function for the concurrent.future that each thread&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-4"&gt;&lt;code data-line-number=" 4"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-4" name="rest_code_03c5c44fafec4c178433057abbcd8db4-4"&gt;&lt;/a&gt;&lt;span class="sd"&gt;      makes use of to query a website. Turns the future's attributes&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-5"&gt;&lt;code data-line-number=" 5"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-5" name="rest_code_03c5c44fafec4c178433057abbcd8db4-5"&gt;&lt;/a&gt;&lt;span class="sd"&gt;      into a message for 'url-to-monitor' topic, then publishes that&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-6"&gt;&lt;code data-line-number=" 6"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-6" name="rest_code_03c5c44fafec4c178433057abbcd8db4-6"&gt;&lt;/a&gt;&lt;span class="sd"&gt;      message to the topic.&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-7"&gt;&lt;code data-line-number=" 7"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-7" name="rest_code_03c5c44fafec4c178433057abbcd8db4-7"&gt;&lt;/a&gt;&lt;span class="sd"&gt;      """&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-8"&gt;&lt;code data-line-number=" 8"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-8" name="rest_code_03c5c44fafec4c178433057abbcd8db4-8"&gt;&lt;/a&gt;      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cancelled&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-9"&gt;&lt;code data-line-number=" 9"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-9" name="rest_code_03c5c44fafec4c178433057abbcd8db4-9"&gt;&lt;/a&gt;          &lt;span class="n"&gt;errmsg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"""Monitor attempt for &lt;/span&gt;&lt;span class="si"&gt;{args}&lt;/span&gt;&lt;span class="s2"&gt; failed"""&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-10"&gt;&lt;code data-line-number="10"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-10" name="rest_code_03c5c44fafec4c178433057abbcd8db4-10"&gt;&lt;/a&gt;          &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errmsg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-11"&gt;&lt;code data-line-number="11"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-11" name="rest_code_03c5c44fafec4c178433057abbcd8db4-11"&gt;&lt;/a&gt;                              &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-12"&gt;&lt;code data-line-number="12"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-12" name="rest_code_03c5c44fafec4c178433057abbcd8db4-12"&gt;&lt;/a&gt;      &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-13"&gt;&lt;code data-line-number="13"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-13" name="rest_code_03c5c44fafec4c178433057abbcd8db4-13"&gt;&lt;/a&gt;          &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msgFields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;())))&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-14"&gt;&lt;code data-line-number="14"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-14" name="rest_code_03c5c44fafec4c178433057abbcd8db4-14"&gt;&lt;/a&gt;          &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin_transaction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-15"&gt;&lt;code data-line-number="15"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-15" name="rest_code_03c5c44fafec4c178433057abbcd8db4-15"&gt;&lt;/a&gt;          &lt;span class="n"&gt;publish_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-16"&gt;&lt;code data-line-number="16"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-16" name="rest_code_03c5c44fafec4c178433057abbcd8db4-16"&gt;&lt;/a&gt;                          &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"url-monitor-results"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-17"&gt;&lt;code data-line-number="17"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-17" name="rest_code_03c5c44fafec4c178433057abbcd8db4-17"&gt;&lt;/a&gt;                          &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'utf-8'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-18"&gt;&lt;code data-line-number="18"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-18" name="rest_code_03c5c44fafec4c178433057abbcd8db4-18"&gt;&lt;/a&gt;          &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit_transaction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-19"&gt;&lt;code data-line-number="19"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-19" name="rest_code_03c5c44fafec4c178433057abbcd8db4-19"&gt;&lt;/a&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-20"&gt;&lt;code data-line-number="20"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-20" name="rest_code_03c5c44fafec4c178433057abbcd8db4-20"&gt;&lt;/a&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-21"&gt;&lt;code data-line-number="21"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-21" name="rest_code_03c5c44fafec4c178433057abbcd8db4-21"&gt;&lt;/a&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fk&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-22"&gt;&lt;code data-line-number="22"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-22" name="rest_code_03c5c44fafec4c178433057abbcd8db4-22"&gt;&lt;/a&gt;      &lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-23"&gt;&lt;code data-line-number="23"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-23" name="rest_code_03c5c44fafec4c178433057abbcd8db4-23"&gt;&lt;/a&gt;&lt;span class="sd"&gt;      Performs an 'HTTP GET' of the supplied url and returns a tuple containing&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-24"&gt;&lt;code data-line-number="24"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-24" name="rest_code_03c5c44fafec4c178433057abbcd8db4-24"&gt;&lt;/a&gt;&lt;span class="sd"&gt;      (fk, start_time, duration, http_status).&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-25"&gt;&lt;code data-line-number="25"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-25" name="rest_code_03c5c44fafec4c178433057abbcd8db4-25"&gt;&lt;/a&gt;&lt;span class="sd"&gt;      The start_time is expressed in milliseconds since the UNIX Epoch.&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-26"&gt;&lt;code data-line-number="26"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-26" name="rest_code_03c5c44fafec4c178433057abbcd8db4-26"&gt;&lt;/a&gt;&lt;span class="sd"&gt;      """&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-27"&gt;&lt;code data-line-number="27"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-27" name="rest_code_03c5c44fafec4c178433057abbcd8db4-27"&gt;&lt;/a&gt;      &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-28"&gt;&lt;code data-line-number="28"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-28" name="rest_code_03c5c44fafec4c178433057abbcd8db4-28"&gt;&lt;/a&gt;      &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-29"&gt;&lt;code data-line-number="29"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-29" name="rest_code_03c5c44fafec4c178433057abbcd8db4-29"&gt;&lt;/a&gt;      &lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_03c5c44fafec4c178433057abbcd8db4-30"&gt;&lt;code data-line-number="30"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_03c5c44fafec4c178433057abbcd8db4-30" name="rest_code_03c5c44fafec4c178433057abbcd8db4-30"&gt;&lt;/a&gt;      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;With the monitoring daemon written, I now needed the other end of the pipe - writing the
&lt;a class="reference external" href="https://kafka.apache.org/documentation/#consumerapi"&gt;Kafka Consumer&lt;/a&gt; to read &lt;em&gt;from&lt;/em&gt; the topic and insert into the database. This was straightforward:
we're polling for messages on both configured topics, when we read one we write it to the
appropriate DB table using a prepared statement, &lt;code class="docutils literal"&gt;commit&lt;/code&gt; and then do it all again
with a while loop.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="code"&gt;&lt;table class="codetable"&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-1"&gt;&lt;code data-line-number=" 1"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-1" name="rest_code_a1d5446b6f9942118f0177235a8e498e-1"&gt;&lt;/a&gt;  &lt;span class="n"&gt;urlToMonitorStmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INSERT INTO urltargets (urltarget, monitor_frequency "&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-2"&gt;&lt;code data-line-number=" 2"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-2" name="rest_code_a1d5446b6f9942118f0177235a8e498e-2"&gt;&lt;/a&gt;  &lt;span class="n"&gt;urlToMonitorStmt&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"VALUES (&lt;/span&gt;&lt;span class="si"&gt;%(urltarget)s&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;%(monitor_frequency)s&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-3"&gt;&lt;code data-line-number=" 3"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-3" name="rest_code_a1d5446b6f9942118f0177235a8e498e-3"&gt;&lt;/a&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-4"&gt;&lt;code data-line-number=" 4"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-4" name="rest_code_a1d5446b6f9942118f0177235a8e498e-4"&gt;&lt;/a&gt;  &lt;span class="n"&gt;urlMonitorResultsStmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INSERT INTO monitor_results (http_status, "&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-5"&gt;&lt;code data-line-number=" 5"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-5" name="rest_code_a1d5446b6f9942118f0177235a8e498e-5"&gt;&lt;/a&gt;  &lt;span class="n"&gt;urlMonitorResultsStmt&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"urltarget_fk, start_time, duration) "&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-6"&gt;&lt;code data-line-number=" 6"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-6" name="rest_code_a1d5446b6f9942118f0177235a8e498e-6"&gt;&lt;/a&gt;  &lt;span class="n"&gt;urlMonitorResultsStmt&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"VALUES (&lt;/span&gt;&lt;span class="si"&gt;%(http_status)s&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;%(targetId)s&lt;/span&gt;&lt;span class="s2"&gt;, "&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-7"&gt;&lt;code data-line-number=" 7"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-7" name="rest_code_a1d5446b6f9942118f0177235a8e498e-7"&gt;&lt;/a&gt;  &lt;span class="n"&gt;urlMonitorResultsStmt&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"to_timestamp(&lt;/span&gt;&lt;span class="si"&gt;%(start_time)s&lt;/span&gt;&lt;span class="s2"&gt;), &lt;/span&gt;&lt;span class="si"&gt;%(duration)s&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-8"&gt;&lt;code data-line-number=" 8"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-8" name="rest_code_a1d5446b6f9942118f0177235a8e498e-8"&gt;&lt;/a&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-9"&gt;&lt;code data-line-number=" 9"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-9" name="rest_code_a1d5446b6f9942118f0177235a8e498e-9"&gt;&lt;/a&gt;  &lt;span class="n"&gt;lookups&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-10"&gt;&lt;code data-line-number="10"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-10" name="rest_code_a1d5446b6f9942118f0177235a8e498e-10"&gt;&lt;/a&gt;      &lt;span class="s2"&gt;"url-to-monitor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;urlToMonitorStmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-11"&gt;&lt;code data-line-number="11"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-11" name="rest_code_a1d5446b6f9942118f0177235a8e498e-11"&gt;&lt;/a&gt;      &lt;span class="s2"&gt;"url-monitor-results"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;urlMonitorResultsStmt&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-12"&gt;&lt;code data-line-number="12"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-12" name="rest_code_a1d5446b6f9942118f0177235a8e498e-12"&gt;&lt;/a&gt;  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-13"&gt;&lt;code data-line-number="13"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-13" name="rest_code_a1d5446b6f9942118f0177235a8e498e-13"&gt;&lt;/a&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-14"&gt;&lt;code data-line-number="14"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-14" name="rest_code_a1d5446b6f9942118f0177235a8e498e-14"&gt;&lt;/a&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"__main__"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-15"&gt;&lt;code data-line-number="15"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-15" name="rest_code_a1d5446b6f9942118f0177235a8e498e-15"&gt;&lt;/a&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-16"&gt;&lt;code data-line-number="16"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-16" name="rest_code_a1d5446b6f9942118f0177235a8e498e-16"&gt;&lt;/a&gt;      &lt;span class="n"&gt;consumer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;setup_kafka_consumer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-17"&gt;&lt;code data-line-number="17"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-17" name="rest_code_a1d5446b6f9942118f0177235a8e498e-17"&gt;&lt;/a&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-18"&gt;&lt;code data-line-number="18"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-18" name="rest_code_a1d5446b6f9942118f0177235a8e498e-18"&gt;&lt;/a&gt;      &lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;setup_db&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-19"&gt;&lt;code data-line-number="19"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-19" name="rest_code_a1d5446b6f9942118f0177235a8e498e-19"&gt;&lt;/a&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-20"&gt;&lt;code data-line-number="20"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-20" name="rest_code_a1d5446b6f9942118f0177235a8e498e-20"&gt;&lt;/a&gt;      &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-21"&gt;&lt;code data-line-number="21"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-21" name="rest_code_a1d5446b6f9942118f0177235a8e498e-21"&gt;&lt;/a&gt;          &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;curs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-22"&gt;&lt;code data-line-number="22"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-22" name="rest_code_a1d5446b6f9942118f0177235a8e498e-22"&gt;&lt;/a&gt;              &lt;span class="n"&gt;msglist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;consume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-23"&gt;&lt;code data-line-number="23"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-23" name="rest_code_a1d5446b6f9942118f0177235a8e498e-23"&gt;&lt;/a&gt;              &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;msglist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-24"&gt;&lt;code data-line-number="24"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-24" name="rest_code_a1d5446b6f9942118f0177235a8e498e-24"&gt;&lt;/a&gt;                  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-25"&gt;&lt;code data-line-number="25"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-25" name="rest_code_a1d5446b6f9942118f0177235a8e498e-25"&gt;&lt;/a&gt;                      &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-26"&gt;&lt;code data-line-number="26"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-26" name="rest_code_a1d5446b6f9942118f0177235a8e498e-26"&gt;&lt;/a&gt;                  &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-27"&gt;&lt;code data-line-number="27"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-27" name="rest_code_a1d5446b6f9942118f0177235a8e498e-27"&gt;&lt;/a&gt;                      &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Received error during poll: &lt;/span&gt;&lt;span class="si"&gt;{error}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-28"&gt;&lt;code data-line-number="28"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-28" name="rest_code_a1d5446b6f9942118f0177235a8e498e-28"&gt;&lt;/a&gt;                          &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-29"&gt;&lt;code data-line-number="29"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-29" name="rest_code_a1d5446b6f9942118f0177235a8e498e-29"&gt;&lt;/a&gt;                  &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-30"&gt;&lt;code data-line-number="30"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-30" name="rest_code_a1d5446b6f9942118f0177235a8e498e-30"&gt;&lt;/a&gt;                      &lt;span class="n"&gt;stmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lookups&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-31"&gt;&lt;code data-line-number="31"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-31" name="rest_code_a1d5446b6f9942118f0177235a8e498e-31"&gt;&lt;/a&gt;                      &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'utf-8'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-32"&gt;&lt;code data-line-number="32"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-32" name="rest_code_a1d5446b6f9942118f0177235a8e498e-32"&gt;&lt;/a&gt;                      &lt;span class="n"&gt;curs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-33"&gt;&lt;code data-line-number="33"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-33" name="rest_code_a1d5446b6f9942118f0177235a8e498e-33"&gt;&lt;/a&gt;              &lt;span class="n"&gt;curs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"COMMIT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-34"&gt;&lt;code data-line-number="34"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-34" name="rest_code_a1d5446b6f9942118f0177235a8e498e-34"&gt;&lt;/a&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="linenos linenodiv"&gt;&lt;a href="https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/#rest_code_a1d5446b6f9942118f0177235a8e498e-35"&gt;&lt;code data-line-number="35"&gt;&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;code&gt;&lt;a id="rest_code_a1d5446b6f9942118f0177235a8e498e-35" name="rest_code_a1d5446b6f9942118f0177235a8e498e-35"&gt;&lt;/a&gt;      &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Of course there should be error handling for the &lt;code class="docutils literal"&gt;execute()&lt;/code&gt;. There should also be packaging
and tests and templates for the report. Do not @ me, etc etc.&lt;/p&gt;
&lt;p&gt;The reason why all these pieces are missing is because the day before I was due to hand this
assignment in to my interviewer, I received a very, very nice offer from another company that
I'd also been interviewing with - and I accepted it.&lt;/p&gt;
&lt;!-- put references after this point --&gt;</description><category>challenges</category><category>Kafka</category><category>programming</category><category>Python</category><category>software engineering</category><guid>https://www.jmcpdotcom.com/blog/posts/2021-05-10-some-python-kafka-stuff/</guid><pubDate>Mon, 10 May 2021 03:00:00 GMT</pubDate></item><item><title>An unexpected live coding challenge</title><link>https://www.jmcpdotcom.com/blog/posts/2021-05-09-live-coding-challenge/</link><dc:creator>jmcp</dc:creator><description>&lt;p&gt;A few weeks ago I was in a technical interview, and was asked to do a live coding challenge. I was surprised,
because this is the sort of thing that I expect a recruiter and hiring manager to mention ahead of time. Preparation
is very important, and while I know that there are many people for whom live coding is a thrill, there are plenty of
other people for whom it can be a terrifying experience.&lt;/p&gt;
&lt;p&gt;I'm glad to say that I'm not terrified by it, but it's definitely not an ideal environment for me.&lt;/p&gt;
&lt;p&gt;So after a few minutes of me describing what I've done in my career (it seemed pretty clear that the interviewer
hadn't actually read my resume), and a few technical questions, we got into the challenge.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_94c4ab25a06341f9af9f82a2930d9494-1" name="rest_code_94c4ab25a06341f9af9f82a2930d9494-1"&gt;&lt;/a&gt;&lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;a id="rest_code_94c4ab25a06341f9af9f82a2930d9494-2" name="rest_code_94c4ab25a06341f9af9f82a2930d9494-2"&gt;&lt;/a&gt;&lt;span class="sd"&gt;For a given string composed of parenthesis ("(", "{", "["), check if the string is valid parenthesis.&lt;/span&gt;
&lt;a id="rest_code_94c4ab25a06341f9af9f82a2930d9494-3" name="rest_code_94c4ab25a06341f9af9f82a2930d9494-3"&gt;&lt;/a&gt;&lt;span class="sd"&gt;1. "()" -- valid&lt;/span&gt;
&lt;a id="rest_code_94c4ab25a06341f9af9f82a2930d9494-4" name="rest_code_94c4ab25a06341f9af9f82a2930d9494-4"&gt;&lt;/a&gt;&lt;span class="sd"&gt;2. "({})" -- valid&lt;/span&gt;
&lt;a id="rest_code_94c4ab25a06341f9af9f82a2930d9494-5" name="rest_code_94c4ab25a06341f9af9f82a2930d9494-5"&gt;&lt;/a&gt;&lt;span class="sd"&gt;3. "(}{)" -- invalid&lt;/span&gt;
&lt;a id="rest_code_94c4ab25a06341f9af9f82a2930d9494-6" name="rest_code_94c4ab25a06341f9af9f82a2930d9494-6"&gt;&lt;/a&gt;&lt;span class="sd"&gt;4. "{()}[{}]" -- valid&lt;/span&gt;
&lt;a id="rest_code_94c4ab25a06341f9af9f82a2930d9494-7" name="rest_code_94c4ab25a06341f9af9f82a2930d9494-7"&gt;&lt;/a&gt;&lt;span class="sd"&gt;5. "({(}))" -- invalid&lt;/span&gt;
&lt;a id="rest_code_94c4ab25a06341f9af9f82a2930d9494-8" name="rest_code_94c4ab25a06341f9af9f82a2930d9494-8"&gt;&lt;/a&gt;&lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;I noted immediately that this is an issue which requires the processing function to track state, because you not
only need to determine open and closed pairings, but also what &lt;em&gt;type&lt;/em&gt; it is.&lt;/p&gt;
&lt;p&gt;It took a minute to sketch out the classifications that I needed, talking through my decision process all the while:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_3a321313d6c24950987b2691da17d836-1" name="rest_code_3a321313d6c24950987b2691da17d836-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;OPENS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"("&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"{"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"["&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_3a321313d6c24950987b2691da17d836-2" name="rest_code_3a321313d6c24950987b2691da17d836-2"&gt;&lt;/a&gt;&lt;span class="n"&gt;CLOSES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;")"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"]"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_3a321313d6c24950987b2691da17d836-3" name="rest_code_3a321313d6c24950987b2691da17d836-3"&gt;&lt;/a&gt;
&lt;a id="rest_code_3a321313d6c24950987b2691da17d836-4" name="rest_code_3a321313d6c24950987b2691da17d836-4"&gt;&lt;/a&gt;&lt;span class="n"&gt;braces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"{"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"}"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_3a321313d6c24950987b2691da17d836-5" name="rest_code_3a321313d6c24950987b2691da17d836-5"&gt;&lt;/a&gt;&lt;span class="n"&gt;parens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"("&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;")"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_3a321313d6c24950987b2691da17d836-6" name="rest_code_3a321313d6c24950987b2691da17d836-6"&gt;&lt;/a&gt;&lt;span class="n"&gt;brackets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"["&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"]"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_3a321313d6c24950987b2691da17d836-7" name="rest_code_3a321313d6c24950987b2691da17d836-7"&gt;&lt;/a&gt;
&lt;a id="rest_code_3a321313d6c24950987b2691da17d836-8" name="rest_code_3a321313d6c24950987b2691da17d836-8"&gt;&lt;/a&gt;&lt;span class="n"&gt;classes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"braces"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;braces&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_3a321313d6c24950987b2691da17d836-9" name="rest_code_3a321313d6c24950987b2691da17d836-9"&gt;&lt;/a&gt;            &lt;span class="s2"&gt;"parens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;parens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_3a321313d6c24950987b2691da17d836-10" name="rest_code_3a321313d6c24950987b2691da17d836-10"&gt;&lt;/a&gt;            &lt;span class="s2"&gt;"brackets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;brackets&lt;/span&gt;
&lt;a id="rest_code_3a321313d6c24950987b2691da17d836-11" name="rest_code_3a321313d6c24950987b2691da17d836-11"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;I was able to stub out a check function pretty quickly, but got stuck when I went from the stub to implementation,
because I realised that I needed to keep track of what the previous element in the string was.&lt;/p&gt;
&lt;p&gt;Oh no! How do I do that? (A stack, btw)&lt;/p&gt;
&lt;p&gt;Mental blank :(&lt;/p&gt;
&lt;p&gt;I needed time to jog my memory, so I asked the interviewer to tell me about himself, what he
does on the team and a few other questions.&lt;/p&gt;
&lt;p&gt;This, I think, was a very good decision - with the focus of the interview &lt;em&gt;not&lt;/em&gt; on me, I could step back and
think about what basic data types in &lt;a class="reference external" href="https://www.python.org"&gt;Python&lt;/a&gt; I could use to implement a stack.&lt;/p&gt;
&lt;p&gt;The data type I needed is indeed pretty basic: a &lt;strong&gt;list()&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A &lt;a class="reference external" href="https://www.python.org"&gt;Python&lt;/a&gt; list() lets you push (the append() operation) and pop so with the addition of another data
structure&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_0b9a9725e60347e6a14edd191169b9ca-1" name="rest_code_0b9a9725e60347e6a14edd191169b9ca-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"braces"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_0b9a9725e60347e6a14edd191169b9ca-2" name="rest_code_0b9a9725e60347e6a14edd191169b9ca-2"&gt;&lt;/a&gt;           &lt;span class="s2"&gt;"parens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_0b9a9725e60347e6a14edd191169b9ca-3" name="rest_code_0b9a9725e60347e6a14edd191169b9ca-3"&gt;&lt;/a&gt;           &lt;span class="s2"&gt;"brackets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;a id="rest_code_0b9a9725e60347e6a14edd191169b9ca-4" name="rest_code_0b9a9725e60347e6a14edd191169b9ca-4"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;and a short function to return the class of the element&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_525b4f4fa7894bc7b819cb0174177f57-1" name="rest_code_525b4f4fa7894bc7b819cb0174177f57-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__classof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_525b4f4fa7894bc7b819cb0174177f57-2" name="rest_code_525b4f4fa7894bc7b819cb0174177f57-2"&gt;&lt;/a&gt;    &lt;span class="sd"&gt;""" returns whether 'c' is in brackets, braces or parens """&lt;/span&gt;
&lt;a id="rest_code_525b4f4fa7894bc7b819cb0174177f57-3" name="rest_code_525b4f4fa7894bc7b819cb0174177f57-3"&gt;&lt;/a&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;braces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_525b4f4fa7894bc7b819cb0174177f57-4" name="rest_code_525b4f4fa7894bc7b819cb0174177f57-4"&gt;&lt;/a&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;"braces"&lt;/span&gt;
&lt;a id="rest_code_525b4f4fa7894bc7b819cb0174177f57-5" name="rest_code_525b4f4fa7894bc7b819cb0174177f57-5"&gt;&lt;/a&gt;    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;brackets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_525b4f4fa7894bc7b819cb0174177f57-6" name="rest_code_525b4f4fa7894bc7b819cb0174177f57-6"&gt;&lt;/a&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;"brackets"&lt;/span&gt;
&lt;a id="rest_code_525b4f4fa7894bc7b819cb0174177f57-7" name="rest_code_525b4f4fa7894bc7b819cb0174177f57-7"&gt;&lt;/a&gt;    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_525b4f4fa7894bc7b819cb0174177f57-8" name="rest_code_525b4f4fa7894bc7b819cb0174177f57-8"&gt;&lt;/a&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;"parens"&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;we're now in a much better position for the algorithm.&lt;/p&gt;
&lt;p&gt;By this time I had also calmed myself down, because everything came together pretty easily for me.&lt;/p&gt;
&lt;p&gt;With the above code blocks already noted, here is the body of the function:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-1" name="rest_code_942deeba85214e24a7b424686a56f4e6-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-2" name="rest_code_942deeba85214e24a7b424686a56f4e6-2"&gt;&lt;/a&gt;    &lt;span class="sd"&gt;""" For the given examples at top, determine validity.&lt;/span&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-3" name="rest_code_942deeba85214e24a7b424686a56f4e6-3"&gt;&lt;/a&gt;&lt;span class="sd"&gt;        Assumption: the input is _only_ braces, parens and brackets&lt;/span&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-4" name="rest_code_942deeba85214e24a7b424686a56f4e6-4"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    """&lt;/span&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-5" name="rest_code_942deeba85214e24a7b424686a56f4e6-5"&gt;&lt;/a&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-6" name="rest_code_942deeba85214e24a7b424686a56f4e6-6"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# start&lt;/span&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-7" name="rest_code_942deeba85214e24a7b424686a56f4e6-7"&gt;&lt;/a&gt;    &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-8" name="rest_code_942deeba85214e24a7b424686a56f4e6-8"&gt;&lt;/a&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-9" name="rest_code_942deeba85214e24a7b424686a56f4e6-9"&gt;&lt;/a&gt;    &lt;span class="n"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-10" name="rest_code_942deeba85214e24a7b424686a56f4e6-10"&gt;&lt;/a&gt;    &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-11" name="rest_code_942deeba85214e24a7b424686a56f4e6-11"&gt;&lt;/a&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-12" name="rest_code_942deeba85214e24a7b424686a56f4e6-12"&gt;&lt;/a&gt;    &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;__classof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-13" name="rest_code_942deeba85214e24a7b424686a56f4e6-13"&gt;&lt;/a&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-14" name="rest_code_942deeba85214e24a7b424686a56f4e6-14"&gt;&lt;/a&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]:&lt;/span&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-15" name="rest_code_942deeba85214e24a7b424686a56f4e6-15"&gt;&lt;/a&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;OPENS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-16" name="rest_code_942deeba85214e24a7b424686a56f4e6-16"&gt;&lt;/a&gt;            &lt;span class="c1"&gt;## increment count &amp;amp; add to stack&lt;/span&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-17" name="rest_code_942deeba85214e24a7b424686a56f4e6-17"&gt;&lt;/a&gt;            &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-18" name="rest_code_942deeba85214e24a7b424686a56f4e6-18"&gt;&lt;/a&gt;            &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;__classof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-19" name="rest_code_942deeba85214e24a7b424686a56f4e6-19"&gt;&lt;/a&gt;        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-20" name="rest_code_942deeba85214e24a7b424686a56f4e6-20"&gt;&lt;/a&gt;            &lt;span class="c1"&gt;## closing checks&lt;/span&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-21" name="rest_code_942deeba85214e24a7b424686a56f4e6-21"&gt;&lt;/a&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__classof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt;  &lt;span class="n"&gt;__classof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-22" name="rest_code_942deeba85214e24a7b424686a56f4e6-22"&gt;&lt;/a&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;"invalid"&lt;/span&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-23" name="rest_code_942deeba85214e24a7b424686a56f4e6-23"&gt;&lt;/a&gt;            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-24" name="rest_code_942deeba85214e24a7b424686a56f4e6-24"&gt;&lt;/a&gt;                &lt;span class="c1"&gt;# decrement count_ (__classof(c))&lt;/span&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-25" name="rest_code_942deeba85214e24a7b424686a56f4e6-25"&gt;&lt;/a&gt;                &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;__classof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-26" name="rest_code_942deeba85214e24a7b424686a56f4e6-26"&gt;&lt;/a&gt;                &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-27" name="rest_code_942deeba85214e24a7b424686a56f4e6-27"&gt;&lt;/a&gt;
&lt;a id="rest_code_942deeba85214e24a7b424686a56f4e6-28" name="rest_code_942deeba85214e24a7b424686a56f4e6-28"&gt;&lt;/a&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;"valid"&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;We're playing fast and loose here with input validity checking - there's no "is this an empty string?" and we're not
handling a single-character string, let alone validating that our input &lt;em&gt;only&lt;/em&gt; contains braces, parens and brackets.&lt;/p&gt;
&lt;p&gt;With this main() function, though, we're doing pretty well:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_1cde50fce4e045918e3d624351ad3ce1-1" name="rest_code_1cde50fce4e045918e3d624351ad3ce1-1"&gt;&lt;/a&gt;&lt;span class="c1"&gt;## main&lt;/span&gt;
&lt;a id="rest_code_1cde50fce4e045918e3d624351ad3ce1-2" name="rest_code_1cde50fce4e045918e3d624351ad3ce1-2"&gt;&lt;/a&gt;&lt;span class="n"&gt;strings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"""()"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_1cde50fce4e045918e3d624351ad3ce1-3" name="rest_code_1cde50fce4e045918e3d624351ad3ce1-3"&gt;&lt;/a&gt;           &lt;span class="sd"&gt;"""({})"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_1cde50fce4e045918e3d624351ad3ce1-4" name="rest_code_1cde50fce4e045918e3d624351ad3ce1-4"&gt;&lt;/a&gt;           &lt;span class="sd"&gt;"""(}{)"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_1cde50fce4e045918e3d624351ad3ce1-5" name="rest_code_1cde50fce4e045918e3d624351ad3ce1-5"&gt;&lt;/a&gt;           &lt;span class="sd"&gt;"""{()}[{}]"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_1cde50fce4e045918e3d624351ad3ce1-6" name="rest_code_1cde50fce4e045918e3d624351ad3ce1-6"&gt;&lt;/a&gt;           &lt;span class="sd"&gt;"""({(}))"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_1cde50fce4e045918e3d624351ad3ce1-7" name="rest_code_1cde50fce4e045918e3d624351ad3ce1-7"&gt;&lt;/a&gt;           &lt;span class="sd"&gt;"""](){}"""&lt;/span&gt;
&lt;a id="rest_code_1cde50fce4e045918e3d624351ad3ce1-8" name="rest_code_1cde50fce4e045918e3d624351ad3ce1-8"&gt;&lt;/a&gt;           &lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_1cde50fce4e045918e3d624351ad3ce1-9" name="rest_code_1cde50fce4e045918e3d624351ad3ce1-9"&gt;&lt;/a&gt;
&lt;a id="rest_code_1cde50fce4e045918e3d624351ad3ce1-10" name="rest_code_1cde50fce4e045918e3d624351ad3ce1-10"&gt;&lt;/a&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_1cde50fce4e045918e3d624351ad3ce1-11" name="rest_code_1cde50fce4e045918e3d624351ad3ce1-11"&gt;&lt;/a&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"""&lt;/span&gt;&lt;span class="si"&gt;{element:20}&lt;/span&gt;&lt;span class="s2"&gt;"""&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;a id="rest_code_1cde50fce4e045918e3d624351ad3ce1-12" name="rest_code_1cde50fce4e045918e3d624351ad3ce1-12"&gt;&lt;/a&gt;          &lt;span class="n"&gt;check_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;which gives us the following output:&lt;/p&gt;
&lt;pre class="code shell"&gt;&lt;a id="rest_code_a75a37f51bc54d8d84ed5627e9a5196d-1" name="rest_code_a75a37f51bc54d8d84ed5627e9a5196d-1"&gt;&lt;/a&gt;&lt;span class="o"&gt;()&lt;/span&gt;                   valid
&lt;a id="rest_code_a75a37f51bc54d8d84ed5627e9a5196d-2" name="rest_code_a75a37f51bc54d8d84ed5627e9a5196d-2"&gt;&lt;/a&gt;&lt;span class="o"&gt;({})&lt;/span&gt;                 valid
&lt;a id="rest_code_a75a37f51bc54d8d84ed5627e9a5196d-3" name="rest_code_a75a37f51bc54d8d84ed5627e9a5196d-3"&gt;&lt;/a&gt;&lt;span class="o"&gt;(}{)&lt;/span&gt;                 invalid
&lt;a id="rest_code_a75a37f51bc54d8d84ed5627e9a5196d-4" name="rest_code_a75a37f51bc54d8d84ed5627e9a5196d-4"&gt;&lt;/a&gt;&lt;span class="o"&gt;{()}[{}]&lt;/span&gt;             valid
&lt;a id="rest_code_a75a37f51bc54d8d84ed5627e9a5196d-5" name="rest_code_a75a37f51bc54d8d84ed5627e9a5196d-5"&gt;&lt;/a&gt;&lt;span class="o"&gt;({(}))&lt;/span&gt;               invalid
&lt;a id="rest_code_a75a37f51bc54d8d84ed5627e9a5196d-6" name="rest_code_a75a37f51bc54d8d84ed5627e9a5196d-6"&gt;&lt;/a&gt;&lt;span class="o"&gt;](){}&lt;/span&gt;                valid
&lt;/pre&gt;&lt;p&gt;Using the criteria specified, the final case is invalid, given that it starts with a terminating rather
than initiating/opening element - there's nothing to balance the element with. However at that point my
time was up and I didn't worry about it.&lt;/p&gt;
&lt;p&gt;My interviewer then asked whether I had considered using recursion to solve the problem.&lt;/p&gt;
&lt;p&gt;I hadn't considered recursion because I generally don't have to for the use-cases I need to write - and
in this particular problem space it didn't seem to me to be an effective use of resources.&lt;/p&gt;
&lt;p&gt;Consider the longest case, &lt;strong&gt;{()}[{}]&lt;/strong&gt;. If you're recursing on the check function, then you'll wind
up calling the function four times, so that's four new stack frames to be created and destroyed. That
doesn't strike me as particularly efficient in time or space. Iterating over the input, however,
avoids all of the setup + teardown overhead.&lt;/p&gt;
&lt;p&gt;Anyway, it was a relatively fun exercise, and I'm glad I did it. I was able to keep a cool head and
buy myself enough time to jog my memory and finish the problem, and it worked the first time (I know,
that _never_ happens!).&lt;/p&gt;
&lt;p&gt;For future encounters like this, I think it's handy to remember these points:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Breathe&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Talk through what you are doing, &lt;em&gt;and why&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you hit a problem, see point 1, and then say that you're stuck and need to think through a particular part of the issue.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you need to &lt;em&gt;stop talking&lt;/em&gt; so you can think, say that that's what you need to do.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It is my impression that if your interviewer is a decent person, they will help you work through
your point of stuckness so that you can complete as much as possible of the task.&lt;/p&gt;
&lt;!-- put references after this point --&gt;</description><category>live programming</category><category>programming</category><category>Python</category><category>software engineering</category><guid>https://www.jmcpdotcom.com/blog/posts/2021-05-09-live-coding-challenge/</guid><pubDate>Sat, 08 May 2021 16:00:00 GMT</pubDate></item><item><title>Access token retrieval in Python and Java</title><link>https://www.jmcpdotcom.com/blog/posts/2020-12-28-access-tokens/</link><dc:creator>jmcp</dc:creator><description>&lt;p&gt;At $work I'm part of our API team, enabling access to the &lt;em&gt;rather large&lt;/em&gt;
datasets that we have acquired (and generated) over the years. We make heavy
use of &lt;a class="reference external" href="https://www.postman.com/"&gt;Postman&lt;/a&gt; for testing, but every now and again I find that I want to
do something on the commandline.&lt;/p&gt;
&lt;p&gt;All of our APIs require authorization, for which we use &lt;a class="reference external" href="https://en.wikipedia.org/wiki/OAuth#OAuth_2.0"&gt;OAuth&lt;/a&gt;, and
specifically the &lt;em&gt;Bearer Token&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Rather than having to fire up &lt;a class="reference external" href="https://www.postman.com/"&gt;Postman&lt;/a&gt; to extract a Bearer Token, I decided
to write a &lt;a class="reference external" href="https://www.python.org"&gt;Python&lt;/a&gt; script to do it for me, so I could then set an
environment variable and pass that to curl as a command-line argument. I was a
little lazy and hard-coded my clientid and secret - I'm not going to be
requesting anybody else's token!&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_ea72aade94644de4bf886ee67a93901f-1" name="rest_code_ea72aade94644de4bf886ee67a93901f-1"&gt;&lt;/a&gt;&lt;span class="ch"&gt;#!/usr/bin/python3&lt;/span&gt;
&lt;a id="rest_code_ea72aade94644de4bf886ee67a93901f-2" name="rest_code_ea72aade94644de4bf886ee67a93901f-2"&gt;&lt;/a&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;
&lt;a id="rest_code_ea72aade94644de4bf886ee67a93901f-3" name="rest_code_ea72aade94644de4bf886ee67a93901f-3"&gt;&lt;/a&gt;
&lt;a id="rest_code_ea72aade94644de4bf886ee67a93901f-4" name="rest_code_ea72aade94644de4bf886ee67a93901f-4"&gt;&lt;/a&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"""https://$AUTHSERVER/access/oauth/token?grant_type=client_credentials&amp;amp;client_id=&lt;/span&gt;&lt;span class="si"&gt;{client_id}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;client_secret=&lt;/span&gt;&lt;span class="si"&gt;{client_secret}&lt;/span&gt;&lt;span class="s2"&gt;"""&lt;/span&gt;
&lt;a id="rest_code_ea72aade94644de4bf886ee67a93901f-5" name="rest_code_ea72aade94644de4bf886ee67a93901f-5"&gt;&lt;/a&gt;&lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"nope"&lt;/span&gt;
&lt;a id="rest_code_ea72aade94644de4bf886ee67a93901f-6" name="rest_code_ea72aade94644de4bf886ee67a93901f-6"&gt;&lt;/a&gt;&lt;span class="n"&gt;client_secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"still_nope"&lt;/span&gt;
&lt;a id="rest_code_ea72aade94644de4bf886ee67a93901f-7" name="rest_code_ea72aade94644de4bf886ee67a93901f-7"&gt;&lt;/a&gt;
&lt;a id="rest_code_ea72aade94644de4bf886ee67a93901f-8" name="rest_code_ea72aade94644de4bf886ee67a93901f-8"&gt;&lt;/a&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_ea72aade94644de4bf886ee67a93901f-9" name="rest_code_ea72aade94644de4bf886ee67a93901f-9"&gt;&lt;/a&gt;                               &lt;span class="n"&gt;client_secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_ea72aade94644de4bf886ee67a93901f-10" name="rest_code_ea72aade94644de4bf886ee67a93901f-10"&gt;&lt;/a&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"export BEARER=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Authorization: Bearer "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
&lt;a id="rest_code_ea72aade94644de4bf886ee67a93901f-11" name="rest_code_ea72aade94644de4bf886ee67a93901f-11"&gt;&lt;/a&gt;      &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s2"&gt;"access_token"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;I'm taking advantage of the fact that I know through many years of use that
the &lt;a class="reference external" href="https://requests.readthedocs.io/en/master/"&gt;requests&lt;/a&gt; package does a lot of heavy lifting for me, particularly the
JSON decoding.&lt;/p&gt;
&lt;p&gt;Since $work has a shutdown between Christmas and New Year, I figured that I
would spent some time implmenting this in Java. Not because I have a need to,
but because I need to get more &lt;a class="reference external" href="https://adoptopenjdk.net/"&gt;Java&lt;/a&gt; under my belt since $work is a &lt;a class="reference external" href="https://adoptopenjdk.net/"&gt;Java&lt;/a&gt;
shop and cross-pollination / polyglotting is useful.&lt;/p&gt;
&lt;p&gt;The first step was to determine how to send an HTTP GET for a specific URI. A
few searches later and I'd arrived at&lt;/p&gt;
&lt;pre class="code java"&gt;&lt;a id="rest_code_646fad754e83415fb25717fee6f4059b-1" name="rest_code_646fad754e83415fb25717fee6f4059b-1"&gt;&lt;/a&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.net.http.HttpClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_646fad754e83415fb25717fee6f4059b-2" name="rest_code_646fad754e83415fb25717fee6f4059b-2"&gt;&lt;/a&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.net.http.HttpRequest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_646fad754e83415fb25717fee6f4059b-3" name="rest_code_646fad754e83415fb25717fee6f4059b-3"&gt;&lt;/a&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.net.http.HttpResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_646fad754e83415fb25717fee6f4059b-4" name="rest_code_646fad754e83415fb25717fee6f4059b-4"&gt;&lt;/a&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.net.http.HttpResponse.BodyHandlers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_646fad754e83415fb25717fee6f4059b-5" name="rest_code_646fad754e83415fb25717fee6f4059b-5"&gt;&lt;/a&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.net.URI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;and since it seems cleaner to import the Exceptions that these throw, I added&lt;/p&gt;
&lt;pre class="code java"&gt;&lt;a id="rest_code_87462c0d1f1b4fd4933bf76189631c37-1" name="rest_code_87462c0d1f1b4fd4933bf76189631c37-1"&gt;&lt;/a&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.io.IOException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_87462c0d1f1b4fd4933bf76189631c37-2" name="rest_code_87462c0d1f1b4fd4933bf76189631c37-2"&gt;&lt;/a&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.net.URISyntaxException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;A bit more hard-coding laziness for the clientid and secret and I had the
beginnings of a solution. (Note that I do &lt;em&gt;not&lt;/em&gt; claim that any of this is using
Best Practices, it's just a building block).&lt;/p&gt;
&lt;pre class="code java"&gt;&lt;a id="rest_code_58d1ee2e59a14cbda0edfa383afaecab-1" name="rest_code_58d1ee2e59a14cbda0edfa383afaecab-1"&gt;&lt;/a&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;bearerTokenCLI&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_58d1ee2e59a14cbda0edfa383afaecab-2" name="rest_code_58d1ee2e59a14cbda0edfa383afaecab-2"&gt;&lt;/a&gt;
&lt;a id="rest_code_58d1ee2e59a14cbda0edfa383afaecab-3" name="rest_code_58d1ee2e59a14cbda0edfa383afaecab-3"&gt;&lt;/a&gt;    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;URI&lt;/span&gt; &lt;span class="n"&gt;authServer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_58d1ee2e59a14cbda0edfa383afaecab-4" name="rest_code_58d1ee2e59a14cbda0edfa383afaecab-4"&gt;&lt;/a&gt;    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_58d1ee2e59a14cbda0edfa383afaecab-5" name="rest_code_58d1ee2e59a14cbda0edfa383afaecab-5"&gt;&lt;/a&gt;
&lt;a id="rest_code_58d1ee2e59a14cbda0edfa383afaecab-6" name="rest_code_58d1ee2e59a14cbda0edfa383afaecab-6"&gt;&lt;/a&gt;    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_58d1ee2e59a14cbda0edfa383afaecab-7" name="rest_code_58d1ee2e59a14cbda0edfa383afaecab-7"&gt;&lt;/a&gt;
&lt;a id="rest_code_58d1ee2e59a14cbda0edfa383afaecab-8" name="rest_code_58d1ee2e59a14cbda0edfa383afaecab-8"&gt;&lt;/a&gt;        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_58d1ee2e59a14cbda0edfa383afaecab-9" name="rest_code_58d1ee2e59a14cbda0edfa383afaecab-9"&gt;&lt;/a&gt;            &lt;span class="n"&gt;authServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_58d1ee2e59a14cbda0edfa383afaecab-10" name="rest_code_58d1ee2e59a14cbda0edfa383afaecab-10"&gt;&lt;/a&gt;                    &lt;span class="s"&gt;"$AUTHSERVER"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_58d1ee2e59a14cbda0edfa383afaecab-11" name="rest_code_58d1ee2e59a14cbda0edfa383afaecab-11"&gt;&lt;/a&gt;                    &lt;span class="s"&gt;"/access/oauth/token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_58d1ee2e59a14cbda0edfa383afaecab-12" name="rest_code_58d1ee2e59a14cbda0edfa383afaecab-12"&gt;&lt;/a&gt;                    &lt;span class="s"&gt;"grant_type=client_credentials"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
&lt;a id="rest_code_58d1ee2e59a14cbda0edfa383afaecab-13" name="rest_code_58d1ee2e59a14cbda0edfa383afaecab-13"&gt;&lt;/a&gt;                            &lt;span class="s"&gt;"&amp;amp;client_id=nope"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
&lt;a id="rest_code_58d1ee2e59a14cbda0edfa383afaecab-14" name="rest_code_58d1ee2e59a14cbda0edfa383afaecab-14"&gt;&lt;/a&gt;                            &lt;span class="s"&gt;"&amp;amp;client_secret=still_node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;a id="rest_code_58d1ee2e59a14cbda0edfa383afaecab-15" name="rest_code_58d1ee2e59a14cbda0edfa383afaecab-15"&gt;&lt;/a&gt;        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;URISyntaxException&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_58d1ee2e59a14cbda0edfa383afaecab-16" name="rest_code_58d1ee2e59a14cbda0edfa383afaecab-16"&gt;&lt;/a&gt;            &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Received URISyntaxException"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;a id="rest_code_58d1ee2e59a14cbda0edfa383afaecab-17" name="rest_code_58d1ee2e59a14cbda0edfa383afaecab-17"&gt;&lt;/a&gt;            &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getReason&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;a id="rest_code_58d1ee2e59a14cbda0edfa383afaecab-18" name="rest_code_58d1ee2e59a14cbda0edfa383afaecab-18"&gt;&lt;/a&gt;            &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;a id="rest_code_58d1ee2e59a14cbda0edfa383afaecab-19" name="rest_code_58d1ee2e59a14cbda0edfa383afaecab-19"&gt;&lt;/a&gt;            &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInput&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;a id="rest_code_58d1ee2e59a14cbda0edfa383afaecab-20" name="rest_code_58d1ee2e59a14cbda0edfa383afaecab-20"&gt;&lt;/a&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;a id="rest_code_58d1ee2e59a14cbda0edfa383afaecab-21" name="rest_code_58d1ee2e59a14cbda0edfa383afaecab-21"&gt;&lt;/a&gt;
&lt;a id="rest_code_58d1ee2e59a14cbda0edfa383afaecab-22" name="rest_code_58d1ee2e59a14cbda0edfa383afaecab-22"&gt;&lt;/a&gt;        &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Requesting "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;authServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Ok so far - we've created a new &lt;em&gt;URI&lt;/em&gt; object, caught the specific exception
that it could throw, and (because I'm learning) printing the stringified
version of the URI to stdout.&lt;/p&gt;
&lt;p&gt;Now we need an &lt;cite&gt;HttpRequest&lt;/cite&gt; to send via an &lt;cite&gt;HttpClient&lt;/cite&gt;:&lt;/p&gt;
&lt;pre class="code java"&gt;&lt;a id="rest_code_eb8ce2b30581422e812b3668401fabf3-1" name="rest_code_eb8ce2b30581422e812b3668401fabf3-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;HttpRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HttpRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authServer&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;a id="rest_code_eb8ce2b30581422e812b3668401fabf3-2" name="rest_code_eb8ce2b30581422e812b3668401fabf3-2"&gt;&lt;/a&gt;&lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newHttpClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;a id="rest_code_eb8ce2b30581422e812b3668401fabf3-3" name="rest_code_eb8ce2b30581422e812b3668401fabf3-3"&gt;&lt;/a&gt;
&lt;a id="rest_code_eb8ce2b30581422e812b3668401fabf3-4" name="rest_code_eb8ce2b30581422e812b3668401fabf3-4"&gt;&lt;/a&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_eb8ce2b30581422e812b3668401fabf3-5" name="rest_code_eb8ce2b30581422e812b3668401fabf3-5"&gt;&lt;/a&gt;    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BodyHandlers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;a id="rest_code_eb8ce2b30581422e812b3668401fabf3-6" name="rest_code_eb8ce2b30581422e812b3668401fabf3-6"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;IOException&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;InterruptedException&lt;/span&gt; &lt;span class="n"&gt;jiie&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_eb8ce2b30581422e812b3668401fabf3-7" name="rest_code_eb8ce2b30581422e812b3668401fabf3-7"&gt;&lt;/a&gt;    &lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;a id="rest_code_eb8ce2b30581422e812b3668401fabf3-8" name="rest_code_eb8ce2b30581422e812b3668401fabf3-8"&gt;&lt;/a&gt;&lt;span class="cm"&gt;     * Note that this catch() uses Java7++ syntax for handling&lt;/span&gt;
&lt;a id="rest_code_eb8ce2b30581422e812b3668401fabf3-9" name="rest_code_eb8ce2b30581422e812b3668401fabf3-9"&gt;&lt;/a&gt;&lt;span class="cm"&gt;     * multiple exceptions in the same block&lt;/span&gt;
&lt;a id="rest_code_eb8ce2b30581422e812b3668401fabf3-10" name="rest_code_eb8ce2b30581422e812b3668401fabf3-10"&gt;&lt;/a&gt;&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;a id="rest_code_eb8ce2b30581422e812b3668401fabf3-11" name="rest_code_eb8ce2b30581422e812b3668401fabf3-11"&gt;&lt;/a&gt;    &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Received java.io.IOException"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;a id="rest_code_eb8ce2b30581422e812b3668401fabf3-12" name="rest_code_eb8ce2b30581422e812b3668401fabf3-12"&gt;&lt;/a&gt;    &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jiie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCause&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;a id="rest_code_eb8ce2b30581422e812b3668401fabf3-13" name="rest_code_eb8ce2b30581422e812b3668401fabf3-13"&gt;&lt;/a&gt;    &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jiie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;a id="rest_code_eb8ce2b30581422e812b3668401fabf3-14" name="rest_code_eb8ce2b30581422e812b3668401fabf3-14"&gt;&lt;/a&gt;    &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jiie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getStackTrace&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;a id="rest_code_eb8ce2b30581422e812b3668401fabf3-15" name="rest_code_eb8ce2b30581422e812b3668401fabf3-15"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Assuming we didn't get an exception, we need to check that the HTTP Status
Code of the response is OK, or 200:&lt;/p&gt;
&lt;pre class="code java"&gt;&lt;a id="rest_code_78fe01ceba0245128c233f7f77e11f04-1" name="rest_code_78fe01ceba0245128c233f7f77e11f04-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_78fe01ceba0245128c233f7f77e11f04-2" name="rest_code_78fe01ceba0245128c233f7f77e11f04-2"&gt;&lt;/a&gt;    &lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;a id="rest_code_78fe01ceba0245128c233f7f77e11f04-3" name="rest_code_78fe01ceba0245128c233f7f77e11f04-3"&gt;&lt;/a&gt;&lt;span class="cm"&gt;     * Something went wrong so print the url we requested, status code,&lt;/span&gt;
&lt;a id="rest_code_78fe01ceba0245128c233f7f77e11f04-4" name="rest_code_78fe01ceba0245128c233f7f77e11f04-4"&gt;&lt;/a&gt;&lt;span class="cm"&gt;     * an error message and the response body as text.&lt;/span&gt;
&lt;a id="rest_code_78fe01ceba0245128c233f7f77e11f04-5" name="rest_code_78fe01ceba0245128c233f7f77e11f04-5"&gt;&lt;/a&gt;&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;a id="rest_code_78fe01ceba0245128c233f7f77e11f04-6" name="rest_code_78fe01ceba0245128c233f7f77e11f04-6"&gt;&lt;/a&gt;    &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Request was unsuccessful. "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
&lt;a id="rest_code_78fe01ceba0245128c233f7f77e11f04-7" name="rest_code_78fe01ceba0245128c233f7f77e11f04-7"&gt;&lt;/a&gt;            &lt;span class="s"&gt;"Received status code "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;a id="rest_code_78fe01ceba0245128c233f7f77e11f04-8" name="rest_code_78fe01ceba0245128c233f7f77e11f04-8"&gt;&lt;/a&gt;    &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"URL requested was\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;authServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;a id="rest_code_78fe01ceba0245128c233f7f77e11f04-9" name="rest_code_78fe01ceba0245128c233f7f77e11f04-9"&gt;&lt;/a&gt;    &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Response body text:\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;a id="rest_code_78fe01ceba0245128c233f7f77e11f04-10" name="rest_code_78fe01ceba0245128c233f7f77e11f04-10"&gt;&lt;/a&gt;    &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;a id="rest_code_78fe01ceba0245128c233f7f77e11f04-11" name="rest_code_78fe01ceba0245128c233f7f77e11f04-11"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;If it isn't ok, we bail out, and otherwise we check for the &lt;em&gt;Content-Type&lt;/em&gt;
header being set to 'application/json'. Why that specific value? If you refer
to the RFCs for OAuth (&lt;a class="reference external" href="https://tools.ietf.org/html/rfc6749"&gt;RFC6749&lt;/a&gt; and &lt;a class="reference external" href="https://tools.ietf.org/html/rfc6750"&gt;RFC6750&lt;/a&gt;) specifically section 5 of
the latter, you see that&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The parameters are included in the entity-body of the HTTP response
using the "application/json" media type as defined by [RFC4627].  The
parameters are serialized into a JavaScript Object Notation (JSON)
structure by adding each parameter at the highest structure level.
Parameter names and string values are included as JSON strings.
Numerical values are included as JSON numbers.  The order of
parameters does not matter and can vary.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let's check for that type then.&lt;/p&gt;
&lt;pre class="code java"&gt;&lt;a id="rest_code_fb6abd0bdb8543f691e49fae20ad9384-1" name="rest_code_fb6abd0bdb8543f691e49fae20ad9384-1"&gt;&lt;/a&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;a id="rest_code_fb6abd0bdb8543f691e49fae20ad9384-2" name="rest_code_fb6abd0bdb8543f691e49fae20ad9384-2"&gt;&lt;/a&gt;&lt;span class="cm"&gt; * Check that we've got 'application/json' as the Content-Type.&lt;/span&gt;
&lt;a id="rest_code_fb6abd0bdb8543f691e49fae20ad9384-3" name="rest_code_fb6abd0bdb8543f691e49fae20ad9384-3"&gt;&lt;/a&gt;&lt;span class="cm"&gt; * Per https://tools.ietf.org/html/rfc7231#section-3.1 we know that&lt;/span&gt;
&lt;a id="rest_code_fb6abd0bdb8543f691e49fae20ad9384-4" name="rest_code_fb6abd0bdb8543f691e49fae20ad9384-4"&gt;&lt;/a&gt;&lt;span class="cm"&gt; * Content-Type is a semicolon-delimited string of&lt;/span&gt;
&lt;a id="rest_code_fb6abd0bdb8543f691e49fae20ad9384-5" name="rest_code_fb6abd0bdb8543f691e49fae20ad9384-5"&gt;&lt;/a&gt;&lt;span class="cm"&gt; *     type/subtype;charset;...&lt;/span&gt;
&lt;a id="rest_code_fb6abd0bdb8543f691e49fae20ad9384-6" name="rest_code_fb6abd0bdb8543f691e49fae20ad9384-6"&gt;&lt;/a&gt;&lt;span class="cm"&gt; * More importantly, we know from https://tools.ietf.org/html/rfc6749#section-5.1&lt;/span&gt;
&lt;a id="rest_code_fb6abd0bdb8543f691e49fae20ad9384-7" name="rest_code_fb6abd0bdb8543f691e49fae20ad9384-7"&gt;&lt;/a&gt;&lt;span class="cm"&gt; * that the response type MUST be JSON.&lt;/span&gt;
&lt;a id="rest_code_fb6abd0bdb8543f691e49fae20ad9384-8" name="rest_code_fb6abd0bdb8543f691e49fae20ad9384-8"&gt;&lt;/a&gt;&lt;span class="cm"&gt; */&lt;/span&gt;
&lt;a id="rest_code_fb6abd0bdb8543f691e49fae20ad9384-9" name="rest_code_fb6abd0bdb8543f691e49fae20ad9384-9"&gt;&lt;/a&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;contentTypeHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;a id="rest_code_fb6abd0bdb8543f691e49fae20ad9384-10" name="rest_code_fb6abd0bdb8543f691e49fae20ad9384-10"&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contentTypeHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_fb6abd0bdb8543f691e49fae20ad9384-11" name="rest_code_fb6abd0bdb8543f691e49fae20ad9384-11"&gt;&lt;/a&gt;    &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ERROR: Content-Type header is empty!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;a id="rest_code_fb6abd0bdb8543f691e49fae20ad9384-12" name="rest_code_fb6abd0bdb8543f691e49fae20ad9384-12"&gt;&lt;/a&gt;    &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;a id="rest_code_fb6abd0bdb8543f691e49fae20ad9384-13" name="rest_code_fb6abd0bdb8543f691e49fae20ad9384-13"&gt;&lt;/a&gt;    &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;a id="rest_code_fb6abd0bdb8543f691e49fae20ad9384-14" name="rest_code_fb6abd0bdb8543f691e49fae20ad9384-14"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Since contentTypeHeader is a &lt;em&gt;List&amp;lt;T&amp;gt;&lt;/em&gt; we can either iterate over it, or,
since we know that it can only occur once in an HTTP response we can grab
element 0 directly. Here's iteration (and yes, I know we should be confirming
that we've actually &lt;em&gt;got&lt;/em&gt; 'application/json', do not @ me, etc etc):&lt;/p&gt;
&lt;pre class="code java"&gt;&lt;a id="rest_code_f5234fd4d456483db1f67573bda6fd5b-1" name="rest_code_f5234fd4d456483db1f67573bda6fd5b-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;contentTypeHeader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_f5234fd4d456483db1f67573bda6fd5b-2" name="rest_code_f5234fd4d456483db1f67573bda6fd5b-2"&gt;&lt;/a&gt;    &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;contentType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;";"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_f5234fd4d456483db1f67573bda6fd5b-3" name="rest_code_f5234fd4d456483db1f67573bda6fd5b-3"&gt;&lt;/a&gt;    &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Actual Content-Type bit:   "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;a id="rest_code_f5234fd4d456483db1f67573bda6fd5b-4" name="rest_code_f5234fd4d456483db1f67573bda6fd5b-4"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;On the other hand, making use of our knowledge that there's only one
Content-Type header in the response, and per &lt;a class="reference external" href="https://tools.ietf.org/html/rfc7231"&gt;RFC7231&lt;/a&gt; we know the format of
the header, we can take a shortcut and grab the value directly:&lt;/p&gt;
&lt;pre class="code java"&gt;&lt;a id="rest_code_432d0726eaac4a0e960e5086afba2e17-1" name="rest_code_432d0726eaac4a0e960e5086afba2e17-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;contentType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contentTypeHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;";"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_432d0726eaac4a0e960e5086afba2e17-2" name="rest_code_432d0726eaac4a0e960e5086afba2e17-2"&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equalsIgnoreCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_432d0726eaac4a0e960e5086afba2e17-3" name="rest_code_432d0726eaac4a0e960e5086afba2e17-3"&gt;&lt;/a&gt;    &lt;span class="cm"&gt;/* Not JSON! */&lt;/span&gt;
&lt;a id="rest_code_432d0726eaac4a0e960e5086afba2e17-4" name="rest_code_432d0726eaac4a0e960e5086afba2e17-4"&gt;&lt;/a&gt;    &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type is "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;contentType&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
&lt;a id="rest_code_432d0726eaac4a0e960e5086afba2e17-5" name="rest_code_432d0726eaac4a0e960e5086afba2e17-5"&gt;&lt;/a&gt;            &lt;span class="s"&gt;" not application/json. Exiting"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;a id="rest_code_432d0726eaac4a0e960e5086afba2e17-6" name="rest_code_432d0726eaac4a0e960e5086afba2e17-6"&gt;&lt;/a&gt;    &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;a id="rest_code_432d0726eaac4a0e960e5086afba2e17-7" name="rest_code_432d0726eaac4a0e960e5086afba2e17-7"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;So far, so good. It took me several hours to get to this point because I not
only had to refresh my memory of those RFCs, but also realising that a
short-circuit was possible.&lt;/p&gt;
&lt;p&gt;Now we can move onto the response body text. By way of printing out
&lt;cite&gt;response.getClass()&lt;/cite&gt; I know that the response is an instance of &lt;cite&gt;class
jdk.internal.net.http.HttpResponseImpl&lt;/cite&gt;, and visual inspection of it shows
that it's JSON. But how do I turn that into an array that I can pull the
&lt;cite&gt;access_token&lt;/cite&gt; information from?&lt;/p&gt;
&lt;p&gt;At first I tried using Google's &lt;a class="reference external" href="https://www.javadoc.io/static/com.google.code.gson/gson/2.8.6/com.google.gson/module-summary.html"&gt;GSON&lt;/a&gt; but I just couldn't get my head around
it. I need to find and understand more code examples. Until I do that,
however, I turned to &lt;a class="reference external" href="https://github.com/FasterXML/jackson-jr"&gt;Jackson JR&lt;/a&gt;, which I found a lot more straightforward.&lt;/p&gt;
&lt;p&gt;We need another import, this time&lt;/p&gt;
&lt;pre class="code java"&gt;&lt;a id="rest_code_971b2d4d01ee464fb8b6238c7c765162-1" name="rest_code_971b2d4d01ee464fb8b6238c7c765162-1"&gt;&lt;/a&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.fasterxml.jackson.jr.ob.JSON&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;And then we construct a &lt;em&gt;Map&amp;lt;String, Object&amp;gt;&lt;/em&gt; from the response body:&lt;/p&gt;
&lt;pre class="code java"&gt;&lt;a id="rest_code_61fdf22996d643c884bf3db3d3cd8f89-1" name="rest_code_61fdf22996d643c884bf3db3d3cd8f89-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_61fdf22996d643c884bf3db3d3cd8f89-2" name="rest_code_61fdf22996d643c884bf3db3d3cd8f89-2"&gt;&lt;/a&gt;    &lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;containerJSON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mapFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;a id="rest_code_61fdf22996d643c884bf3db3d3cd8f89-3" name="rest_code_61fdf22996d643c884bf3db3d3cd8f89-3"&gt;&lt;/a&gt;    &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;containerJSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"access_token"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;a id="rest_code_61fdf22996d643c884bf3db3d3cd8f89-4" name="rest_code_61fdf22996d643c884bf3db3d3cd8f89-4"&gt;&lt;/a&gt;    &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"export BEARER=\"BEARER "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"\"\n"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;a id="rest_code_61fdf22996d643c884bf3db3d3cd8f89-5" name="rest_code_61fdf22996d643c884bf3db3d3cd8f89-5"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IOException&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_61fdf22996d643c884bf3db3d3cd8f89-6" name="rest_code_61fdf22996d643c884bf3db3d3cd8f89-6"&gt;&lt;/a&gt;    &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Caught exception "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;a id="rest_code_61fdf22996d643c884bf3db3d3cd8f89-7" name="rest_code_61fdf22996d643c884bf3db3d3cd8f89-7"&gt;&lt;/a&gt;    &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Message:\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;a id="rest_code_61fdf22996d643c884bf3db3d3cd8f89-8" name="rest_code_61fdf22996d643c884bf3db3d3cd8f89-8"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;You'll observe that I'm again being a bit lazy here by wrapping this block in
the one &lt;em&gt;try {...} catch (..) {..}&lt;/em&gt; block. Whyso?  Because by this point we
should be certain that we've actually got an &lt;em&gt;access_token&lt;/em&gt; element in the
response, and if we don't then there's something going wrong upstream.&lt;/p&gt;
&lt;p&gt;Finally, how do we build this thing? As much as I'd like to just run &lt;em&gt;javac&lt;/em&gt;
over the source and create a solitary jar, I've found that including external
dependencies is made immensely easier by using a build system like &lt;a class="reference external" href="https://maven.apache.org"&gt;Maven&lt;/a&gt;,
&lt;a class="reference external" href="https://ant.apache.org"&gt;Ant&lt;/a&gt; or &lt;a class="reference external" href="https://gradle.org"&gt;Gradle&lt;/a&gt; in the Java ecosystem. For C, of course, there's no place
like &lt;a class="reference external" href="https://docs.oracle.com/cd/E86824_01/html/E54763/make-1s.html#REFMAN1make-1s"&gt;make(1s)&lt;/a&gt; (ok, or &lt;a class="reference external" href="https://www.gnu.org/software/make/manual/"&gt;GNU Make&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;I started with using a &lt;a class="reference external" href="https://maven.apache.org/guides/introduction/introduction-to-archetypes.html"&gt;Maven *archetype*&lt;/a&gt;, added this dependency to
&lt;em&gt;pom.xml&lt;/em&gt;:&lt;/p&gt;
&lt;pre class="code xml"&gt;&lt;a id="rest_code_55d927f7a07949a283df48f9c624a7e8-1" name="rest_code_55d927f7a07949a283df48f9c624a7e8-1"&gt;&lt;/a&gt;&lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_55d927f7a07949a283df48f9c624a7e8-2" name="rest_code_55d927f7a07949a283df48f9c624a7e8-2"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_55d927f7a07949a283df48f9c624a7e8-3" name="rest_code_55d927f7a07949a283df48f9c624a7e8-3"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.fasterxml.jackson.jr&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_55d927f7a07949a283df48f9c624a7e8-4" name="rest_code_55d927f7a07949a283df48f9c624a7e8-4"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;jackson-jr-objects&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_55d927f7a07949a283df48f9c624a7e8-5" name="rest_code_55d927f7a07949a283df48f9c624a7e8-5"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.12.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_55d927f7a07949a283df48f9c624a7e8-6" name="rest_code_55d927f7a07949a283df48f9c624a7e8-6"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_55d927f7a07949a283df48f9c624a7e8-7" name="rest_code_55d927f7a07949a283df48f9c624a7e8-7"&gt;&lt;/a&gt;&lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;and added the &lt;a class="reference external" href="https://maven.apache.org/plugins/maven-assembly-plugin/"&gt;Maven Assembly plugin&lt;/a&gt; to the &lt;em&gt;&amp;lt;build&amp;gt;&lt;/em&gt; lifecycle. Then
building was a matter of&lt;/p&gt;
&lt;pre class="code shell"&gt;&lt;a id="rest_code_5ebb15d29a4f43d1837ab2d60818e93e-1" name="rest_code_5ebb15d29a4f43d1837ab2d60818e93e-1"&gt;&lt;/a&gt;$ mvn clean install package assembly:single
&lt;/pre&gt;&lt;p&gt;and then I could run the package with&lt;/p&gt;
&lt;pre class="code shell"&gt;&lt;a id="rest_code_4e9092483bc14fafb6198c3f9626ca97-1" name="rest_code_4e9092483bc14fafb6198c3f9626ca97-1"&gt;&lt;/a&gt;$ java -jar target/bearer_token_cli-1.0-SNAPSHOT-jar-with-dependencies.jar
&lt;/pre&gt;&lt;p&gt;All up, I estimate that researching and writing this in Java took me about 12
hours. Most of which was ecosystem research and exploration. There was only
one syntax issue which tripped me up - I needed an Array and for about 10
minutes was searching through &lt;a class="reference external" href="https://docs.oracle.com/javase/8/docs/api/java/lang/package-summary.html"&gt;Javadocs&lt;/a&gt; for an appropriate class before I
remembered I could use &lt;cite&gt;String[] arrName&lt;/cite&gt;. Sigh.&lt;/p&gt;
&lt;p&gt;Learning the ecosystem is the thing I'm finding most difficult with Java -
it's &lt;em&gt;huge&lt;/em&gt; and there are so many different classes to solve overlapping
problems. I haven't even begun to work with &lt;cite&gt;@Annotations&lt;/cite&gt; or dependency
injection for my own code yet. Truth be told, after a decade+ of working in
Solaris, the idea that anything could be injected into the code I've written
puts a chill down my spine. I'm sure I'll get past it one day.&lt;/p&gt;
&lt;!-- put references after this point --&gt;</description><category>Java</category><category>JWT</category><category>OAuth</category><category>programming</category><category>Python</category><category>software engineering</category><guid>https://www.jmcpdotcom.com/blog/posts/2020-12-28-access-tokens/</guid><pubDate>Sun, 27 Dec 2020 17:00:00 GMT</pubDate></item><item><title>A brief introduction to the Python Database API</title><link>https://www.jmcpdotcom.com/blog/posts/2020-06-03-a-brief-introduction-to-the-python-database-api/</link><dc:creator>jmcp</dc:creator><description>&lt;p&gt;As popular as NoSQL databases currently are, there is still an immense amount
of data in the world for which the relational database is still the most appropriate
way to store, access and manipulate it. For those of us who like to use &lt;a class="reference external" href="https://www.python.org"&gt;Python&lt;/a&gt;
rather than &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Java_Database_Connectivity"&gt;JDBC&lt;/a&gt;, we are fortunate to have bindings for the major databases
which implement the &lt;a class="reference external" href="https://www.python.org/dev/peps/pep-0249/"&gt;Python Database API&lt;/a&gt;, aka &lt;a class="reference external" href="https://www.python.org/dev/peps/pep-0249/"&gt;PEP249&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This standard is important because it allows you to write your application in a way
that promotes cross-platform (cross-database engine) portability. While you do still
need to be aware of differences between the databases (for example, Oracle's &lt;strong&gt;VARCHAR2&lt;/strong&gt;
vs PostgreSQL's &lt;strong&gt;VARCHAR&lt;/strong&gt;), the essential tasks that you need to accomplish when
making use of a database are abstracted from you.&lt;/p&gt;
&lt;p&gt;In this brief introduction I will show you how to install two popular Python database
binding packages, connect to a database and run queries.&lt;/p&gt;
&lt;section id="installing-the-bits-you-need"&gt;
&lt;h2&gt;Installing the bits you need&lt;/h2&gt;
&lt;p&gt;I assume that you've got &lt;a class="reference external" href="https://www.python.org"&gt;Python&lt;/a&gt; installed on your system already, and that the database
you want to connect to is set up and running. What you need now are the &lt;em&gt;bindings&lt;/em&gt; for
that particular database. For the &lt;a class="reference external" href="https://www.oracle.com/database/technologies"&gt;Oracle database&lt;/a&gt;, this is a package known as &lt;a class="reference external" href="https://oracle.github.io/python-cx_Oracle/"&gt;cx_Oracle&lt;/a&gt;.
For &lt;a class="reference external" href="https://www.postgresql.org"&gt;PostgreSQL&lt;/a&gt;, I suggest &lt;a class="reference external" href="https://www.psycopg.org/"&gt;psycopg2&lt;/a&gt; though there are other bindings available.&lt;/p&gt;
&lt;p&gt;You will &lt;strong&gt;definitely&lt;/strong&gt; need to install the pre-packaged database client libraries on your
host (or in your container) as well. For &lt;a class="reference external" href="https://www.oracle.com"&gt;Oracle&lt;/a&gt; look for your platform in the
&lt;a class="reference external" href="https://www.oracle.com/database/technologies/instant-client.html"&gt;Oracle Instant Client&lt;/a&gt; download pages, for &lt;a class="reference external" href="https://www.postgresql.org"&gt;PostgreSQL&lt;/a&gt; on linux you will need &lt;em&gt;libpq-dev&lt;/em&gt;
as supplied by your package manager. On MacOS, please find the appropriate option from
the &lt;a class="reference external" href="https://www.postgresql.org/download/macosx/"&gt;PostgreSQL MacOSX download page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After you've installed these pieces, you can then &lt;code class="docutils literal"&gt;pip install &lt;span class="pre"&gt;--user&lt;/span&gt; cx_Oracle&lt;/code&gt; or
&lt;code class="docutils literal"&gt;pip install &lt;span class="pre"&gt;--user&lt;/span&gt; psycopg2&lt;/code&gt;. My personal preference is to install bindings in a &lt;em&gt;venv&lt;/em&gt;.
For &lt;a class="reference external" href="https://www.oracle.com/solaris/solaris11/"&gt;Solaris 11&lt;/a&gt; you can get the &lt;a class="reference external" href="https://oracle.github.io/python-cx_Oracle/"&gt;cx_Oracle&lt;/a&gt; bits &lt;em&gt;as well as&lt;/em&gt; the &lt;a class="reference external" href="https://www.oracle.com/database/technologies/instant-client.html"&gt;Oracle Instant Client&lt;/a&gt;
by uttering &lt;code class="docutils literal"&gt;pkg install cx_Oracle&lt;/code&gt;. [On a personal note, I made that possible - see the
History tab on the &lt;a class="reference external" href="https://github.com/oracle/solaris-userland/commits/master/components/python/cx_oracle"&gt;Solaris Userland cx_Oracle github&lt;/a&gt;].&lt;/p&gt;
&lt;/section&gt;
&lt;section id="getting-a-connection-to-the-database"&gt;
&lt;h2&gt;Getting a connection to the database&lt;/h2&gt;
&lt;p&gt;Now that you have the correct packages installed, you can start investigating. The first
thing to do is start an interactive interpreter and import the modules you need:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_abfb6788c9434518a054bd547bdd02d2-1" name="rest_code_abfb6788c9434518a054bd547bdd02d2-1"&gt;&lt;/a&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;python3&lt;/span&gt;&lt;span class="mf"&gt;.8&lt;/span&gt;
&lt;a id="rest_code_abfb6788c9434518a054bd547bdd02d2-2" name="rest_code_abfb6788c9434518a054bd547bdd02d2-2"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cx_Oracle&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;(Note that unless you're using Solaris' pre-packaged version, you &lt;strong&gt;must&lt;/strong&gt; point LD_LIBRARY_PATH to where
the module can locate the Instant Client libraries, specifically &lt;cite&gt;libclntsh.so&lt;/cite&gt;)&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_16aa56354cc844bf90df47b8cb53e6cf-1" name="rest_code_16aa56354cc844bf90df47b8cb53e6cf-1"&gt;&lt;/a&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="mf"&gt;3.8&lt;/span&gt;
&lt;a id="rest_code_16aa56354cc844bf90df47b8cb53e6cf-2" name="rest_code_16aa56354cc844bf90df47b8cb53e6cf-2"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;psycopg2&lt;/span&gt;
&lt;a id="rest_code_16aa56354cc844bf90df47b8cb53e6cf-3" name="rest_code_16aa56354cc844bf90df47b8cb53e6cf-3"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;psycopg2.extras&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;execute_batch&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;We need a connection to the database, which in Oracle terms is a DSN, or &lt;em&gt;Data Source Name&lt;/em&gt;.
This is made up of the username, password, host (either address or IP), port and database instance name.
Put together, the host, port and database instance are what Oracle calls the "service name".&lt;/p&gt;
&lt;p&gt;The common example you will see in Oracle documentation (and on &lt;a class="reference external" href="https://stackoverflow.com"&gt;stackoverflow&lt;/a&gt;!) is&lt;/p&gt;
&lt;pre class="code shell"&gt;&lt;a id="rest_code_869b5525b0cc4179ae908a0b8852d966-1" name="rest_code_869b5525b0cc4179ae908a0b8852d966-1"&gt;&lt;/a&gt;scott/tiger@orcl:1521/orcl
&lt;/pre&gt;&lt;p&gt;I'm a bit over seeing that, so I've created a different username and DBname:&lt;/p&gt;
&lt;pre class="code shell"&gt;&lt;a id="rest_code_9b4f019440f848258a8d620dd01b7050-1" name="rest_code_9b4f019440f848258a8d620dd01b7050-1"&gt;&lt;/a&gt;DEMOUSER/DemoDbUser1@dbhost:1521/demodb
&lt;/pre&gt;&lt;p&gt;Since Oracle defaults to using port 1521 on the host, you only need to specify the port number if your
database is listening on a different port.&lt;/p&gt;
&lt;p&gt;An Oracle connection is therefore&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_06ecf7f1fdb04d8280ae41469dea3270-1" name="rest_code_06ecf7f1fdb04d8280ae41469dea3270-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"DEMOUSER"&lt;/span&gt;
&lt;a id="rest_code_06ecf7f1fdb04d8280ae41469dea3270-2" name="rest_code_06ecf7f1fdb04d8280ae41469dea3270-2"&gt;&lt;/a&gt;&lt;span class="n"&gt;passwd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"DemoDbUser1"&lt;/span&gt;
&lt;a id="rest_code_06ecf7f1fdb04d8280ae41469dea3270-3" name="rest_code_06ecf7f1fdb04d8280ae41469dea3270-3"&gt;&lt;/a&gt;&lt;span class="n"&gt;servicename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dbhost:1521/demodb"&lt;/span&gt;
&lt;a id="rest_code_06ecf7f1fdb04d8280ae41469dea3270-4" name="rest_code_06ecf7f1fdb04d8280ae41469dea3270-4"&gt;&lt;/a&gt;
&lt;a id="rest_code_06ecf7f1fdb04d8280ae41469dea3270-5" name="rest_code_06ecf7f1fdb04d8280ae41469dea3270-5"&gt;&lt;/a&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_06ecf7f1fdb04d8280ae41469dea3270-6" name="rest_code_06ecf7f1fdb04d8280ae41469dea3270-6"&gt;&lt;/a&gt;    &lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cx_Oracle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;passwd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;servicename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_06ecf7f1fdb04d8280ae41469dea3270-7" name="rest_code_06ecf7f1fdb04d8280ae41469dea3270-7"&gt;&lt;/a&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;cx_Oracle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DatabaseError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;dbe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_06ecf7f1fdb04d8280ae41469dea3270-8" name="rest_code_06ecf7f1fdb04d8280ae41469dea3270-8"&gt;&lt;/a&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"""Unable to obtain a connection to &lt;/span&gt;&lt;span class="si"&gt;{servicename}&lt;/span&gt;&lt;span class="s2"&gt;"""&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;servicename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;servicename&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_06ecf7f1fdb04d8280ae41469dea3270-9" name="rest_code_06ecf7f1fdb04d8280ae41469dea3270-9"&gt;&lt;/a&gt;    &lt;span class="k"&gt;raise&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;With &lt;a class="reference external" href="https://www.postgresql.org"&gt;PostgreSQL&lt;/a&gt; we can also specify whether we want SSL enabled.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_978c35b3b41c4f11b112b443de988cfc-1" name="rest_code_978c35b3b41c4f11b112b443de988cfc-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_978c35b3b41c4f11b112b443de988cfc-2" name="rest_code_978c35b3b41c4f11b112b443de988cfc-2"&gt;&lt;/a&gt;    &lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;psycopg2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dbname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dbname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_978c35b3b41c4f11b112b443de988cfc-3" name="rest_code_978c35b3b41c4f11b112b443de988cfc-3"&gt;&lt;/a&gt;                                  &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dbuser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_978c35b3b41c4f11b112b443de988cfc-4" name="rest_code_978c35b3b41c4f11b112b443de988cfc-4"&gt;&lt;/a&gt;                                  &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dbpassword&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_978c35b3b41c4f11b112b443de988cfc-5" name="rest_code_978c35b3b41c4f11b112b443de988cfc-5"&gt;&lt;/a&gt;                                  &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dbhost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_978c35b3b41c4f11b112b443de988cfc-6" name="rest_code_978c35b3b41c4f11b112b443de988cfc-6"&gt;&lt;/a&gt;                                  &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dbport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_978c35b3b41c4f11b112b443de988cfc-7" name="rest_code_978c35b3b41c4f11b112b443de988cfc-7"&gt;&lt;/a&gt;                                  &lt;span class="n"&gt;sslmode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dbsslmode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_978c35b3b41c4f11b112b443de988cfc-8" name="rest_code_978c35b3b41c4f11b112b443de988cfc-8"&gt;&lt;/a&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;psycopg2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OperationalError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;dboe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_978c35b3b41c4f11b112b443de988cfc-9" name="rest_code_978c35b3b41c4f11b112b443de988cfc-9"&gt;&lt;/a&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"""Unable to obtain a connection to the database.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&lt;/span&gt;
&lt;a id="rest_code_978c35b3b41c4f11b112b443de988cfc-10" name="rest_code_978c35b3b41c4f11b112b443de988cfc-10"&gt;&lt;/a&gt;&lt;span class="s2"&gt;          Please check your database connection details&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&lt;/span&gt;
&lt;a id="rest_code_978c35b3b41c4f11b112b443de988cfc-11" name="rest_code_978c35b3b41c4f11b112b443de988cfc-11"&gt;&lt;/a&gt;&lt;span class="s2"&gt;          Notify dbadmin@&lt;/span&gt;&lt;span class="si"&gt;{dbhost}&lt;/span&gt;&lt;span class="s2"&gt;"""&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dbhost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dbhost&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;a id="rest_code_978c35b3b41c4f11b112b443de988cfc-12" name="rest_code_978c35b3b41c4f11b112b443de988cfc-12"&gt;&lt;/a&gt;          &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_978c35b3b41c4f11b112b443de988cfc-13" name="rest_code_978c35b3b41c4f11b112b443de988cfc-13"&gt;&lt;/a&gt;    &lt;span class="k"&gt;raise&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Once we have the connection, we need a cursor so we can execute statements:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_9c09af6825224ea09ecc582c2a544036-1" name="rest_code_9c09af6825224ea09ecc582c2a544036-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Now that we have our connection and a cursor to use, what can we do with it? SQL baby!&lt;/p&gt;
&lt;p&gt;We need a handy dataset to muck around with, so I've got just the thing courtesy of
my previous post about &lt;a class="reference external" href="https://www.jmcpdotcom.com/blog/posts/2019-09-27-microservices-part-1/"&gt;determining your electorate&lt;/a&gt;. My JSON files have a fairly
basic structure reflecting my needs for that project, but that is still sufficient
our purposes here.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_b87ce28d4dd04d2b9d61c1128d3dc3d7-1" name="rest_code_b87ce28d4dd04d2b9d61c1128d3dc3d7-1"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;a id="rest_code_b87ce28d4dd04d2b9d61c1128d3dc3d7-2" name="rest_code_b87ce28d4dd04d2b9d61c1128d3dc3d7-2"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;actf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"find-my-electorate/json/ACT.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"r"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_b87ce28d4dd04d2b9d61c1128d3dc3d7-3" name="rest_code_b87ce28d4dd04d2b9d61c1128d3dc3d7-3"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;actf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a id="rest_code_b87ce28d4dd04d2b9d61c1128d3dc3d7-4" name="rest_code_b87ce28d4dd04d2b9d61c1128d3dc3d7-4"&gt;&lt;/a&gt;&lt;span class="n"&gt;dict_keys&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'Brindabella'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Ginninderra'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Kurrajong'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Murrumbidgee'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Yerrabi'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;a id="rest_code_b87ce28d4dd04d2b9d61c1128d3dc3d7-5" name="rest_code_b87ce28d4dd04d2b9d61c1128d3dc3d7-5"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;actf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Yerrabi"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a id="rest_code_b87ce28d4dd04d2b9d61c1128d3dc3d7-6" name="rest_code_b87ce28d4dd04d2b9d61c1128d3dc3d7-6"&gt;&lt;/a&gt;&lt;span class="n"&gt;dict_keys&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'jurisdiction'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'locality'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'blocks'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'coords'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;The 'jurisdiction' key is the state or territory name (ACT, Australian Capital Territory in this case),
the 'locality' is the electorate name, 'blocks' is a list of the  &lt;a class="reference external" href="https://www.abs.gov.au"&gt;Australian Bureau of Statistics&lt;/a&gt;
&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Meshblock#Australia"&gt;Mesh Block&lt;/a&gt; which the electorate contains, and 'coords' is a list of the latitude, longitude pairs
which are the boundaries of those blocks.&lt;/p&gt;
&lt;p&gt;Now we need a schema to operate within. This is reasonably simple - if you've done any work with
SQL databases. To start with, we need two &lt;em&gt;SEQUENCE&lt;/em&gt; s, which are database objects from which
any database user may generate a unique integer. This is the easiest and cheapest way that I know
of to generate values for primary key columns. (For a good explanation on them, see the
&lt;a class="reference external" href="https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/CREATE-SEQUENCE.html#GUID-E9C78A8C-615A-4757-B2A8-5E6EFB130571"&gt;Oracle 19c CREATE SEQUENCE&lt;/a&gt; page). After that, we'll create two tables: electorate and geopoints.
(We're going to ignore the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Meshblock#Australia"&gt;Mesh Block&lt;/a&gt; data, it's not useful for this example).&lt;/p&gt;
&lt;pre class="code SQL"&gt;&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-1" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-1"&gt;&lt;/a&gt;&lt;span class="c1"&gt;-- This is the Oracle DB form&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-2" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-2"&gt;&lt;/a&gt;&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;GEOPOINTS&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINTS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-3" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-3"&gt;&lt;/a&gt;&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;ELECTORATES&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINTS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-4" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-4"&gt;&lt;/a&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-5" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-5"&gt;&lt;/a&gt;&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="n"&gt;SEQUENCE&lt;/span&gt; &lt;span class="n"&gt;ELECTORATE_SEQ&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-6" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-6"&gt;&lt;/a&gt;&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="n"&gt;SEQUENCE&lt;/span&gt; &lt;span class="n"&gt;LATLONG_SEQ&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-7" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-7"&gt;&lt;/a&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-8" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-8"&gt;&lt;/a&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;SEQUENCE&lt;/span&gt; &lt;span class="n"&gt;ELECTORATE_SEQ&lt;/span&gt; &lt;span class="k"&gt;INCREMENT&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;MINVALUE&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;NOMAXVALUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-9" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-9"&gt;&lt;/a&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;SEQUENCE&lt;/span&gt; &lt;span class="n"&gt;LATLONG_SEQ&lt;/span&gt; &lt;span class="k"&gt;INCREMENT&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;MINVALUE&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;NOMAXVALUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-10" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-10"&gt;&lt;/a&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-11" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-11"&gt;&lt;/a&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;ELECTORATES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-12" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-12"&gt;&lt;/a&gt;    &lt;span class="n"&gt;ELECTORATE_PK&lt;/span&gt;   &lt;span class="nb"&gt;NUMBER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;DEMOUSER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ELECTORATE_SEQ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NEXTVAL&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-13" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-13"&gt;&lt;/a&gt;    &lt;span class="n"&gt;ELECTORATE&lt;/span&gt;      &lt;span class="n"&gt;VARCHAR2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-14" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-14"&gt;&lt;/a&gt;    &lt;span class="n"&gt;STATENAME&lt;/span&gt;       &lt;span class="n"&gt;VARCHAR2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="c1"&gt;-- Using the abbreviated form&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-15" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-15"&gt;&lt;/a&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-16" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-16"&gt;&lt;/a&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-17" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-17"&gt;&lt;/a&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;GEOPOINTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-18" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-18"&gt;&lt;/a&gt;    &lt;span class="n"&gt;LATLONG_PK&lt;/span&gt;      &lt;span class="nb"&gt;NUMBER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;DEMOUSER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LATLONG_SEQ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NEXTVAL&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-19" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-19"&gt;&lt;/a&gt;    &lt;span class="n"&gt;LATITUDE&lt;/span&gt;        &lt;span class="nb"&gt;NUMBER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-20" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-20"&gt;&lt;/a&gt;    &lt;span class="n"&gt;LONGITUDE&lt;/span&gt;       &lt;span class="nb"&gt;NUMBER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-21" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-21"&gt;&lt;/a&gt;    &lt;span class="n"&gt;ELECTORATE_FK&lt;/span&gt;   &lt;span class="nb"&gt;NUMBER&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;ELECTORATES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ELECTORATE_PK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-22" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-22"&gt;&lt;/a&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-23" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-23"&gt;&lt;/a&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-24" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-24"&gt;&lt;/a&gt;&lt;span class="c1"&gt;-- Now for the PostgreSQL form&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-25" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-25"&gt;&lt;/a&gt;&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;GEOPOINTS&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-26" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-26"&gt;&lt;/a&gt;&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;ELECTORATES&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-27" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-27"&gt;&lt;/a&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-28" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-28"&gt;&lt;/a&gt;&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="n"&gt;SEQUENCE&lt;/span&gt; &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;ELECTORATE_SEQ&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-29" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-29"&gt;&lt;/a&gt;&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="n"&gt;SEQUENCE&lt;/span&gt; &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;LATLONG_SEQ&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-30" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-30"&gt;&lt;/a&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-31" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-31"&gt;&lt;/a&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;SEQUENCE&lt;/span&gt; &lt;span class="n"&gt;ELECTORATE_SEQ&lt;/span&gt; &lt;span class="k"&gt;INCREMENT&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;MINVALUE&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;NO&lt;/span&gt; &lt;span class="k"&gt;MAXVALUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-32" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-32"&gt;&lt;/a&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;SEQUENCE&lt;/span&gt; &lt;span class="n"&gt;LATLONG_SEQ&lt;/span&gt; &lt;span class="k"&gt;INCREMENT&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;MINVALUE&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;NO&lt;/span&gt; &lt;span class="k"&gt;MAXVALUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-33" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-33"&gt;&lt;/a&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-34" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-34"&gt;&lt;/a&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;ELECTORATES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-35" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-35"&gt;&lt;/a&gt;    &lt;span class="n"&gt;ELECTORATE_PK&lt;/span&gt;   &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NEXTVAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ELECTORATE_SEQ'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-36" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-36"&gt;&lt;/a&gt;    &lt;span class="n"&gt;ELECTORATE&lt;/span&gt;      &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-37" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-37"&gt;&lt;/a&gt;    &lt;span class="n"&gt;STATENAME&lt;/span&gt;       &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="c1"&gt;-- Using the abbreviated form&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-38" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-38"&gt;&lt;/a&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-39" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-39"&gt;&lt;/a&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-40" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-40"&gt;&lt;/a&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;GEOPOINTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-41" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-41"&gt;&lt;/a&gt;    &lt;span class="n"&gt;LATLONG_PK&lt;/span&gt;      &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NEXTVAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'LATLONG_SEQ'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-42" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-42"&gt;&lt;/a&gt;    &lt;span class="n"&gt;LATITUDE&lt;/span&gt;        &lt;span class="nb"&gt;NUMERIC&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-43" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-43"&gt;&lt;/a&gt;    &lt;span class="n"&gt;LONGITUDE&lt;/span&gt;       &lt;span class="nb"&gt;NUMERIC&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-44" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-44"&gt;&lt;/a&gt;    &lt;span class="n"&gt;ELECTORATE_FK&lt;/span&gt;   &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;ELECTORATES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ELECTORATE_PK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;a id="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-45" name="rest_code_c4f14f494c244b02bf719f5dbb0fa6b0-45"&gt;&lt;/a&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;We need to execute these statements one by one, using &lt;cite&gt;cursor.execute()&lt;/cite&gt;. Come back once you've
done that and we've got our schema set up.&lt;/p&gt;
&lt;p&gt;[I see that a short time has passed - welcome back]&lt;/p&gt;
&lt;p&gt;Now we need to populate our database. For both connection types, we'll make use of prepared
statements, which allow us to insert, update, delete or select many rows at a time. For the
Oracle connection we'll use the &lt;cite&gt;executemany()&lt;/cite&gt; function, but for PostgreSQL's &lt;a class="reference external" href="https://www.psycopg.org/"&gt;psycopg2&lt;/a&gt;
bindings we'll use &lt;cite&gt;execute_batch()&lt;/cite&gt; instead. (See the &lt;a class="reference external" href="https://www.psycopg.org/docs/extras.html?highlight=prepared#fast-execution-helpers"&gt;psycopg2 website note&lt;/a&gt; about it).&lt;/p&gt;
&lt;p&gt;For the Oracle examples, we'll use a bind variable so that when we &lt;cite&gt;INSERT&lt;/cite&gt; into
the &lt;cite&gt;ELECTORATES&lt;/cite&gt; table we get the &lt;cite&gt;ELECTORATE_PK&lt;/cite&gt; to use in the subsequent &lt;cite&gt;INSERT&lt;/cite&gt; to the
&lt;cite&gt;GEOPOINTS&lt;/cite&gt; table. That saves us a query to get that information. The &lt;a class="reference external" href="https://www.psycopg.org/"&gt;psycopg2&lt;/a&gt; module
does not have this support, unfortunately. Since we have many records to process, I've created
some small functions to enable &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself"&gt;DRY&lt;/a&gt;&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-1" name="rest_code_009e643fbf124a0886afb3bc2578cccc-1"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Oracle version&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-2" name="rest_code_009e643fbf124a0886afb3bc2578cccc-2"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;epk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-3" name="rest_code_009e643fbf124a0886afb3bc2578cccc-3"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;electStmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"""INSERT INTO ELECTORATES (ELECTORATE, STATENAME)&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-4" name="rest_code_009e643fbf124a0886afb3bc2578cccc-4"&gt;&lt;/a&gt;&lt;span class="s2"&gt;...                VALUES (:electorate, :statename)&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-5" name="rest_code_009e643fbf124a0886afb3bc2578cccc-5"&gt;&lt;/a&gt;&lt;span class="s2"&gt;...                RETURNING ELECTORATE_PK INTO :epk"""&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-6" name="rest_code_009e643fbf124a0886afb3bc2578cccc-6"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;geoStmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"""INSERT INTO GEOPOINTS(LATITUDE, LONGITUDE, ELECTORATE_FK)&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-7" name="rest_code_009e643fbf124a0886afb3bc2578cccc-7"&gt;&lt;/a&gt;&lt;span class="s2"&gt;...              VALUES (:lat, :long, :fk)"""&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-8" name="rest_code_009e643fbf124a0886afb3bc2578cccc-8"&gt;&lt;/a&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-9" name="rest_code_009e643fbf124a0886afb3bc2578cccc-9"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;addstate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-10" name="rest_code_009e643fbf124a0886afb3bc2578cccc-10"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;electorate&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;statename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-11" name="rest_code_009e643fbf124a0886afb3bc2578cccc-11"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="n"&gt;edict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"electorate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;statename&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;electorate&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"locality"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-12" name="rest_code_009e643fbf124a0886afb3bc2578cccc-12"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;                  &lt;span class="s2"&gt;"statename"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;statename&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;electorate&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"jurisdiction"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-13" name="rest_code_009e643fbf124a0886afb3bc2578cccc-13"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;                  &lt;span class="s2"&gt;"epk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;epk&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-14" name="rest_code_009e643fbf124a0886afb3bc2578cccc-14"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;electStmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-15" name="rest_code_009e643fbf124a0886afb3bc2578cccc-15"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-16" name="rest_code_009e643fbf124a0886afb3bc2578cccc-16"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;latlong&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;statename&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;electorate&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"coords"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-17" name="rest_code_009e643fbf124a0886afb3bc2578cccc-17"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;             &lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;"latitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latlong&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-18" name="rest_code_009e643fbf124a0886afb3bc2578cccc-18"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;                            &lt;span class="s2"&gt;"longitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latlong&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-19" name="rest_code_009e643fbf124a0886afb3bc2578cccc-19"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;                            &lt;span class="s2"&gt;"fk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epk&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getvalue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])})&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-20" name="rest_code_009e643fbf124a0886afb3bc2578cccc-20"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;executemany&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;geoStmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-21" name="rest_code_009e643fbf124a0886afb3bc2578cccc-21"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-22" name="rest_code_009e643fbf124a0886afb3bc2578cccc-22"&gt;&lt;/a&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-23" name="rest_code_009e643fbf124a0886afb3bc2578cccc-23"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;allpoints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-24" name="rest_code_009e643fbf124a0886afb3bc2578cccc-24"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;states&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"act"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"nsw"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"nt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"qld"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"sa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"tas"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"wa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"vic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"federal"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-25" name="rest_code_009e643fbf124a0886afb3bc2578cccc-25"&gt;&lt;/a&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-26" name="rest_code_009e643fbf124a0886afb3bc2578cccc-26"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;states&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-27" name="rest_code_009e643fbf124a0886afb3bc2578cccc-27"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;allpoints&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"json/&lt;/span&gt;&lt;span class="si"&gt;{stu}&lt;/span&gt;&lt;span class="s2"&gt;.json"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stu&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="s2"&gt;"r"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_009e643fbf124a0886afb3bc2578cccc-28" name="rest_code_009e643fbf124a0886afb3bc2578cccc-28"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;addstate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allpoints&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;For the PostgreSQL version, we need to subtly change the &lt;cite&gt;INSERT&lt;/cite&gt; statements:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_a7737c2f0b754830bcd5a283a49c00e4-1" name="rest_code_a7737c2f0b754830bcd5a283a49c00e4-1"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;electStmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"""INSERT INTO ELECTORATES (ELECTORATE, STATENAME)&lt;/span&gt;
&lt;a id="rest_code_a7737c2f0b754830bcd5a283a49c00e4-2" name="rest_code_a7737c2f0b754830bcd5a283a49c00e4-2"&gt;&lt;/a&gt;&lt;span class="s2"&gt;...                VALUES (&lt;/span&gt;&lt;span class="si"&gt;%(electorate)s&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;%(statename)s&lt;/span&gt;&lt;span class="s2"&gt;)&lt;/span&gt;
&lt;a id="rest_code_a7737c2f0b754830bcd5a283a49c00e4-3" name="rest_code_a7737c2f0b754830bcd5a283a49c00e4-3"&gt;&lt;/a&gt;&lt;span class="s2"&gt;...                RETURNING ELECTORATE_PK"""&lt;/span&gt;
&lt;a id="rest_code_a7737c2f0b754830bcd5a283a49c00e4-4" name="rest_code_a7737c2f0b754830bcd5a283a49c00e4-4"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;geoStmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"""INSERT INTO GEOPOINTS(LATITUDE, LONGITUDE, ELECTORATE_FK)&lt;/span&gt;
&lt;a id="rest_code_a7737c2f0b754830bcd5a283a49c00e4-5" name="rest_code_a7737c2f0b754830bcd5a283a49c00e4-5"&gt;&lt;/a&gt;&lt;span class="s2"&gt;...                VALUES (&lt;/span&gt;&lt;span class="si"&gt;%(latitude)s&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;%(longitude)s&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;%(fk)s&lt;/span&gt;&lt;span class="s2"&gt;)"""&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Another thing we need to change is the first execute, because bind variables are an
Oracle extension, and we're also going to change from &lt;cite&gt;executemany()&lt;/cite&gt; to &lt;cite&gt;execute_batch()&lt;/cite&gt;:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_60e897fb25464af4a7e2e8009e83600a-1" name="rest_code_60e897fb25464af4a7e2e8009e83600a-1"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;addstate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_60e897fb25464af4a7e2e8009e83600a-2" name="rest_code_60e897fb25464af4a7e2e8009e83600a-2"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;electorate&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;statename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_60e897fb25464af4a7e2e8009e83600a-3" name="rest_code_60e897fb25464af4a7e2e8009e83600a-3"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="n"&gt;edict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"electorate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;statename&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;electorate&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"locality"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;a id="rest_code_60e897fb25464af4a7e2e8009e83600a-4" name="rest_code_60e897fb25464af4a7e2e8009e83600a-4"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;                  &lt;span class="s2"&gt;"statename"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;statename&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;electorate&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"jurisdiction"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;a id="rest_code_60e897fb25464af4a7e2e8009e83600a-5" name="rest_code_60e897fb25464af4a7e2e8009e83600a-5"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;electStmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_60e897fb25464af4a7e2e8009e83600a-6" name="rest_code_60e897fb25464af4a7e2e8009e83600a-6"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="n"&gt;epk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_60e897fb25464af4a7e2e8009e83600a-7" name="rest_code_60e897fb25464af4a7e2e8009e83600a-7"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a id="rest_code_60e897fb25464af4a7e2e8009e83600a-8" name="rest_code_60e897fb25464af4a7e2e8009e83600a-8"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;latlong&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;statename&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;electorate&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"coords"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;a id="rest_code_60e897fb25464af4a7e2e8009e83600a-9" name="rest_code_60e897fb25464af4a7e2e8009e83600a-9"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;             &lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;"latitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latlong&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
&lt;a id="rest_code_60e897fb25464af4a7e2e8009e83600a-10" name="rest_code_60e897fb25464af4a7e2e8009e83600a-10"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;                            &lt;span class="s2"&gt;"longitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latlong&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="s2"&gt;"fk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;epk&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;a id="rest_code_60e897fb25464af4a7e2e8009e83600a-11" name="rest_code_60e897fb25464af4a7e2e8009e83600a-11"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="n"&gt;execute_batch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;geoStmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_60e897fb25464af4a7e2e8009e83600a-12" name="rest_code_60e897fb25464af4a7e2e8009e83600a-12"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;The rest of the data loading is the same as with Oracle. Since I'm curious about
efficiencies, I did a rough benchmark of the data load with &lt;cite&gt;executemany()&lt;/cite&gt;  as well,
and it was around half the speed of using &lt;cite&gt;execute_batch()&lt;/cite&gt;. YMMV, of course, so always
test your code with &lt;em&gt;your&lt;/em&gt; data and as close to real-world conditions as possible.&lt;/p&gt;
&lt;p&gt;Now that we have the data loaded we can do some investigations. I'm going to show the
PostgreSQL output for this and call out differences with Oracle where necessary.&lt;/p&gt;
&lt;p&gt;While some parts of our state and territory borders follow rivers and mountain ranges, quite a &lt;em&gt;lot&lt;/em&gt; of
them follow specific latitudes and longitudes. The border between Queensland and New South Wales, for
example is mostly along the 29th parallel. Between the Northern Territory and South Australia it's the
26th parallel, and that between South Australia and Western Australia is along meridian 129 East.&lt;/p&gt;
&lt;p&gt;If you go to a mapping service and plug in 29S,141E (the line between Queensland and New South Wales):&lt;/p&gt;
&lt;a class="reference external image-reference" href="https://www.google.com.au/maps/place/29%C2%B000'00.0%22S+141%C2%B000'00.0%22E/@-28.9999953,140.9978113,17z/data=!3m1!4b1!4m5!3m4!1s0x0:0x0!8m2!3d-29!4d141"&gt;&lt;img alt="/images/2020/python-db-api/29s141e-400x400.png" src="https://www.jmcpdotcom.com/blog/images/2020/python-db-api/29s141e-400x400.png"&gt;&lt;/a&gt;
&lt;p&gt;you'll see that the &lt;em&gt;exact&lt;/em&gt; point is not where the boundary is actually drawn. That means we need to use
some fuzziness in our matching.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-1" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-1"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"""SELECT STATENAME, ELECTORATE FROM ELECTORATES E WHERE ELECTORATE_PK IN&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-2" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-2"&gt;&lt;/a&gt;&lt;span class="s2"&gt;...                   (SELECT DISTINCT ELECTORATE_FK FROM GEOPOINTS WHERE LATITUDE BETWEEN -29.001 AND -28.995)&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-3" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-3"&gt;&lt;/a&gt;&lt;span class="s2"&gt;...                   ORDER BY STATENAME, ELECTORATE"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-4" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-4"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-5" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-5"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-6" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-6"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{s:6}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;{e}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-7" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-7"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-8" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-8"&gt;&lt;/a&gt;&lt;span class="n"&gt;NSW&lt;/span&gt;    &lt;span class="n"&gt;Ballina&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-9" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-9"&gt;&lt;/a&gt;&lt;span class="n"&gt;NSW&lt;/span&gt;    &lt;span class="n"&gt;Barwon&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-10" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-10"&gt;&lt;/a&gt;&lt;span class="n"&gt;NSW&lt;/span&gt;    &lt;span class="n"&gt;Clarence&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-11" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-11"&gt;&lt;/a&gt;&lt;span class="n"&gt;NSW&lt;/span&gt;    &lt;span class="n"&gt;Lismore&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-12" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-12"&gt;&lt;/a&gt;&lt;span class="n"&gt;NSW&lt;/span&gt;    &lt;span class="n"&gt;New&lt;/span&gt; &lt;span class="n"&gt;England&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-13" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-13"&gt;&lt;/a&gt;&lt;span class="n"&gt;NSW&lt;/span&gt;    &lt;span class="n"&gt;Northern&lt;/span&gt; &lt;span class="n"&gt;Tablelands&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-14" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-14"&gt;&lt;/a&gt;&lt;span class="n"&gt;NSW&lt;/span&gt;    &lt;span class="n"&gt;Page&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-15" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-15"&gt;&lt;/a&gt;&lt;span class="n"&gt;NSW&lt;/span&gt;    &lt;span class="n"&gt;Parkes&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-16" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-16"&gt;&lt;/a&gt;&lt;span class="n"&gt;QLD&lt;/span&gt;    &lt;span class="n"&gt;Maranoa&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-17" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-17"&gt;&lt;/a&gt;&lt;span class="n"&gt;QLD&lt;/span&gt;    &lt;span class="n"&gt;Southern&lt;/span&gt; &lt;span class="n"&gt;Downs&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-18" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-18"&gt;&lt;/a&gt;&lt;span class="n"&gt;QLD&lt;/span&gt;    &lt;span class="n"&gt;Warrego&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-19" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-19"&gt;&lt;/a&gt;&lt;span class="n"&gt;SA&lt;/span&gt;     &lt;span class="n"&gt;Giles&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-20" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-20"&gt;&lt;/a&gt;&lt;span class="n"&gt;SA&lt;/span&gt;     &lt;span class="n"&gt;Grey&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-21" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-21"&gt;&lt;/a&gt;&lt;span class="n"&gt;SA&lt;/span&gt;     &lt;span class="n"&gt;Stuart&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-22" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-22"&gt;&lt;/a&gt;&lt;span class="n"&gt;SA&lt;/span&gt;     &lt;span class="n"&gt;Stuart&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-23" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-23"&gt;&lt;/a&gt;&lt;span class="n"&gt;WA&lt;/span&gt;     &lt;span class="n"&gt;Durack&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-24" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-24"&gt;&lt;/a&gt;&lt;span class="n"&gt;WA&lt;/span&gt;     &lt;span class="n"&gt;Geraldton&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-25" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-25"&gt;&lt;/a&gt;&lt;span class="n"&gt;WA&lt;/span&gt;     &lt;span class="n"&gt;Kalgoorlie&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-26" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-26"&gt;&lt;/a&gt;&lt;span class="n"&gt;WA&lt;/span&gt;     &lt;span class="n"&gt;Moore&lt;/span&gt;
&lt;a id="rest_code_84f093f2bf88492eb64d8ea93fe64905-27" name="rest_code_84f093f2bf88492eb64d8ea93fe64905-27"&gt;&lt;/a&gt;&lt;span class="n"&gt;WA&lt;/span&gt;     &lt;span class="n"&gt;North&lt;/span&gt; &lt;span class="n"&gt;West&lt;/span&gt; &lt;span class="n"&gt;Central&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Hmm. Not only did I not want SA or WA electorates returned, the database has also given me the &lt;em&gt;federal&lt;/em&gt; electorates as well.
Let's update our electorates table to reflect that jurisdictional issue:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_16e3b7661d3746409c3569db4be4cd98-1" name="rest_code_16e3b7661d3746409c3569db4be4cd98-1"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"""ALTER TABLE ELECTORATES ADD FEDERAL BOOLEAN"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_16e3b7661d3746409c3569db4be4cd98-2" name="rest_code_16e3b7661d3746409c3569db4be4cd98-2"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Popping into a &lt;cite&gt;psql&lt;/cite&gt; session for a moment, let's see what we have:&lt;/p&gt;
&lt;pre class="code shell"&gt;&lt;a id="rest_code_e90cea1e2db14364b5c0b9ba665bf662-1" name="rest_code_e90cea1e2db14364b5c0b9ba665bf662-1"&gt;&lt;/a&gt;&lt;span class="nv"&gt;demodb&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt; &lt;span class="se"&gt;\d&lt;/span&gt; electorates
&lt;a id="rest_code_e90cea1e2db14364b5c0b9ba665bf662-2" name="rest_code_e90cea1e2db14364b5c0b9ba665bf662-2"&gt;&lt;/a&gt;                                    Table &lt;span class="s2"&gt;"public.electorates"&lt;/span&gt;
&lt;a id="rest_code_e90cea1e2db14364b5c0b9ba665bf662-3" name="rest_code_e90cea1e2db14364b5c0b9ba665bf662-3"&gt;&lt;/a&gt;    Column     &lt;span class="p"&gt;|&lt;/span&gt;         Type          &lt;span class="p"&gt;|&lt;/span&gt; Collation &lt;span class="p"&gt;|&lt;/span&gt; Nullable &lt;span class="p"&gt;|&lt;/span&gt;               Default
&lt;a id="rest_code_e90cea1e2db14364b5c0b9ba665bf662-4" name="rest_code_e90cea1e2db14364b5c0b9ba665bf662-4"&gt;&lt;/a&gt;---------------+-----------------------+-----------+----------+-------------------------------------
&lt;a id="rest_code_e90cea1e2db14364b5c0b9ba665bf662-5" name="rest_code_e90cea1e2db14364b5c0b9ba665bf662-5"&gt;&lt;/a&gt; electorate_pk &lt;span class="p"&gt;|&lt;/span&gt; integer               &lt;span class="p"&gt;|&lt;/span&gt;           &lt;span class="p"&gt;|&lt;/span&gt; not null &lt;span class="p"&gt;|&lt;/span&gt; nextval&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'electorate_seq'&lt;/span&gt;::regclass&lt;span class="o"&gt;)&lt;/span&gt;
&lt;a id="rest_code_e90cea1e2db14364b5c0b9ba665bf662-6" name="rest_code_e90cea1e2db14364b5c0b9ba665bf662-6"&gt;&lt;/a&gt; electorate    &lt;span class="p"&gt;|&lt;/span&gt; character varying&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;           &lt;span class="p"&gt;|&lt;/span&gt; not null &lt;span class="p"&gt;|&lt;/span&gt;
&lt;a id="rest_code_e90cea1e2db14364b5c0b9ba665bf662-7" name="rest_code_e90cea1e2db14364b5c0b9ba665bf662-7"&gt;&lt;/a&gt; statename     &lt;span class="p"&gt;|&lt;/span&gt; character varying&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;|&lt;/span&gt;           &lt;span class="p"&gt;|&lt;/span&gt; not null &lt;span class="p"&gt;|&lt;/span&gt;
&lt;a id="rest_code_e90cea1e2db14364b5c0b9ba665bf662-8" name="rest_code_e90cea1e2db14364b5c0b9ba665bf662-8"&gt;&lt;/a&gt; federal       &lt;span class="p"&gt;|&lt;/span&gt; boolean               &lt;span class="p"&gt;|&lt;/span&gt;           &lt;span class="p"&gt;|&lt;/span&gt;          &lt;span class="p"&gt;|&lt;/span&gt;
&lt;a id="rest_code_e90cea1e2db14364b5c0b9ba665bf662-9" name="rest_code_e90cea1e2db14364b5c0b9ba665bf662-9"&gt;&lt;/a&gt;Indexes:
&lt;a id="rest_code_e90cea1e2db14364b5c0b9ba665bf662-10" name="rest_code_e90cea1e2db14364b5c0b9ba665bf662-10"&gt;&lt;/a&gt;    &lt;span class="s2"&gt;"electorates_pkey"&lt;/span&gt; PRIMARY KEY, btree &lt;span class="o"&gt;(&lt;/span&gt;electorate_pk&lt;span class="o"&gt;)&lt;/span&gt;
&lt;a id="rest_code_e90cea1e2db14364b5c0b9ba665bf662-11" name="rest_code_e90cea1e2db14364b5c0b9ba665bf662-11"&gt;&lt;/a&gt;Referenced by:
&lt;a id="rest_code_e90cea1e2db14364b5c0b9ba665bf662-12" name="rest_code_e90cea1e2db14364b5c0b9ba665bf662-12"&gt;&lt;/a&gt;    TABLE &lt;span class="s2"&gt;"geopoints"&lt;/span&gt; CONSTRAINT &lt;span class="s2"&gt;"geopoints_electorate_fk_fkey"&lt;/span&gt; FOREIGN KEY &lt;span class="o"&gt;(&lt;/span&gt;electorate_fk&lt;span class="o"&gt;)&lt;/span&gt; REFERENCES electorates&lt;span class="o"&gt;(&lt;/span&gt;electorate_pk&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Now let's populate that column. Unfortunately, though, some state electorates have the same name as federal electorates - and
some electorate names exist in more than one state, too! (I'm looking at you, Bass!). Tempting as it is to zorch our db and
start from scratch, I'm going to take advantage of this information:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;We added the federal electorate list after all the states,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the federal list was constructed starting with the ACT, and therefore&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Federal electorates will have a higher primary key value than all the states and territories.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With that in mind here's the first federal electorate entry:&lt;/p&gt;
&lt;pre class="code shell"&gt;&lt;a id="rest_code_92a473cd465f4548bfe84dfec6fe1a86-1" name="rest_code_92a473cd465f4548bfe84dfec6fe1a86-1"&gt;&lt;/a&gt;&lt;span class="nv"&gt;demodb&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt; SELECT ELECTORATE_PK, ELECTORATE, STATENAME FROM ELECTORATES WHERE &lt;span class="nv"&gt;STATENAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ACT'&lt;/span&gt; ORDER BY ELECTORATE_PK, STATENAME&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_92a473cd465f4548bfe84dfec6fe1a86-2" name="rest_code_92a473cd465f4548bfe84dfec6fe1a86-2"&gt;&lt;/a&gt; electorate_pk &lt;span class="p"&gt;|&lt;/span&gt;  electorate  &lt;span class="p"&gt;|&lt;/span&gt; statename
&lt;a id="rest_code_92a473cd465f4548bfe84dfec6fe1a86-3" name="rest_code_92a473cd465f4548bfe84dfec6fe1a86-3"&gt;&lt;/a&gt;---------------+--------------+-----------
&lt;a id="rest_code_92a473cd465f4548bfe84dfec6fe1a86-4" name="rest_code_92a473cd465f4548bfe84dfec6fe1a86-4"&gt;&lt;/a&gt;             &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; Brindabella  &lt;span class="p"&gt;|&lt;/span&gt; ACT
&lt;a id="rest_code_92a473cd465f4548bfe84dfec6fe1a86-5" name="rest_code_92a473cd465f4548bfe84dfec6fe1a86-5"&gt;&lt;/a&gt;             &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; Ginninderra  &lt;span class="p"&gt;|&lt;/span&gt; ACT
&lt;a id="rest_code_92a473cd465f4548bfe84dfec6fe1a86-6" name="rest_code_92a473cd465f4548bfe84dfec6fe1a86-6"&gt;&lt;/a&gt;             &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; Kurrajong    &lt;span class="p"&gt;|&lt;/span&gt; ACT
&lt;a id="rest_code_92a473cd465f4548bfe84dfec6fe1a86-7" name="rest_code_92a473cd465f4548bfe84dfec6fe1a86-7"&gt;&lt;/a&gt;             &lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; Murrumbidgee &lt;span class="p"&gt;|&lt;/span&gt; ACT
&lt;a id="rest_code_92a473cd465f4548bfe84dfec6fe1a86-8" name="rest_code_92a473cd465f4548bfe84dfec6fe1a86-8"&gt;&lt;/a&gt;             &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; Yerrabi      &lt;span class="p"&gt;|&lt;/span&gt; ACT
&lt;a id="rest_code_92a473cd465f4548bfe84dfec6fe1a86-9" name="rest_code_92a473cd465f4548bfe84dfec6fe1a86-9"&gt;&lt;/a&gt;           &lt;span class="m"&gt;416&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; Bean         &lt;span class="p"&gt;|&lt;/span&gt; ACT
&lt;a id="rest_code_92a473cd465f4548bfe84dfec6fe1a86-10" name="rest_code_92a473cd465f4548bfe84dfec6fe1a86-10"&gt;&lt;/a&gt;           &lt;span class="m"&gt;417&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; Canberra     &lt;span class="p"&gt;|&lt;/span&gt; ACT
&lt;a id="rest_code_92a473cd465f4548bfe84dfec6fe1a86-11" name="rest_code_92a473cd465f4548bfe84dfec6fe1a86-11"&gt;&lt;/a&gt;           &lt;span class="m"&gt;418&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; Fenner       &lt;span class="p"&gt;|&lt;/span&gt; ACT
&lt;a id="rest_code_92a473cd465f4548bfe84dfec6fe1a86-12" name="rest_code_92a473cd465f4548bfe84dfec6fe1a86-12"&gt;&lt;/a&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt; rows&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Let's check how many electorates have a primary key &lt;em&gt;higher&lt;/em&gt; than Bean:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_7670b2b022af4c27b07f4dad899d55d0-1" name="rest_code_7670b2b022af4c27b07f4dad899d55d0-1"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"""SELECT COUNT(ELECTORATE_PK), MAX(ELECTORATE_PK) FROM ELECTORATES"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_7670b2b022af4c27b07f4dad899d55d0-2" name="rest_code_7670b2b022af4c27b07f4dad899d55d0-2"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a id="rest_code_7670b2b022af4c27b07f4dad899d55d0-3" name="rest_code_7670b2b022af4c27b07f4dad899d55d0-3"&gt;&lt;/a&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="mi"&gt;566&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;566&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;And a quick check to see that we do in fact have 151 electorates with that condition:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_b40e64c765a44f2abeedcd8693a9cdc0-1" name="rest_code_b40e64c765a44f2abeedcd8693a9cdc0-1"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;566&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;415&lt;/span&gt;
&lt;a id="rest_code_b40e64c765a44f2abeedcd8693a9cdc0-2" name="rest_code_b40e64c765a44f2abeedcd8693a9cdc0-2"&gt;&lt;/a&gt;&lt;span class="mi"&gt;151&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;We do. Onwards.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_4d3cb694879e4cf29f2a93cc70a2ab01-1" name="rest_code_4d3cb694879e4cf29f2a93cc70a2ab01-1"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"""UPDATE ELECTORATES SET FEDERAL = TRUE WHERE ELECTORATE_PK &amp;gt; 415"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_4d3cb694879e4cf29f2a93cc70a2ab01-2" name="rest_code_4d3cb694879e4cf29f2a93cc70a2ab01-2"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a id="rest_code_4d3cb694879e4cf29f2a93cc70a2ab01-3" name="rest_code_4d3cb694879e4cf29f2a93cc70a2ab01-3"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"""SELECT COUNT(*) FROM ELECTORATES WHERE FEDERAL IS TRUE"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_4d3cb694879e4cf29f2a93cc70a2ab01-4" name="rest_code_4d3cb694879e4cf29f2a93cc70a2ab01-4"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a id="rest_code_4d3cb694879e4cf29f2a93cc70a2ab01-5" name="rest_code_4d3cb694879e4cf29f2a93cc70a2ab01-5"&gt;&lt;/a&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="mi"&gt;151&lt;/span&gt;&lt;span class="p"&gt;,)]&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Likewise, we'll set the others to &lt;cite&gt;federal=False&lt;/cite&gt;:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_c262ab0087cd4b74b61a28bede615c25-1" name="rest_code_c262ab0087cd4b74b61a28bede615c25-1"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"""UPDATE ELECTORATES SET FEDERAL = FALSE WHERE ELECTORATE_PK &amp;lt; 416"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_c262ab0087cd4b74b61a28bede615c25-2" name="rest_code_c262ab0087cd4b74b61a28bede615c25-2"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Back to our queries. I want to see both sorts of electorates, but grouped by whether they are federal or
state electorates:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-1" name="rest_code_942574733e6f4f4c973987c88301bea4-1"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"""SELECT E.STATENAME, E.ELECTORATE, E.FEDERAL FROM ELECTORATES E&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-2" name="rest_code_942574733e6f4f4c973987c88301bea4-2"&gt;&lt;/a&gt;&lt;span class="s2"&gt;...                   WHERE E.ELECTORATE_PK IN&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-3" name="rest_code_942574733e6f4f4c973987c88301bea4-3"&gt;&lt;/a&gt;&lt;span class="s2"&gt;...                       (SELECT DISTINCT ELECTORATE_FK FROM GEOPOINTS WHERE LATITUDE BETWEEN -29.001 AND -28.995)&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-4" name="rest_code_942574733e6f4f4c973987c88301bea4-4"&gt;&lt;/a&gt;&lt;span class="s2"&gt;...                   GROUP BY E.STATENAME, E.ELECTORATE, E.FEDERAL&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-5" name="rest_code_942574733e6f4f4c973987c88301bea4-5"&gt;&lt;/a&gt;&lt;span class="s2"&gt;...                   ORDER BY STATENAME, ELECTORATE"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-6" name="rest_code_942574733e6f4f4c973987c88301bea4-6"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fedstate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-7" name="rest_code_942574733e6f4f4c973987c88301bea4-7"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fedstate&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-8" name="rest_code_942574733e6f4f4c973987c88301bea4-8"&gt;&lt;/a&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s1"&gt;'NSW'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Ballina'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'NSW'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Barwon'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'NSW'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Clarence'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'NSW'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Lismore'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'NSW'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'New England'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'NSW'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Northern Tablelands'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'NSW'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Page'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'NSW'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Parkes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'QLD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Maranoa'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'QLD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Southern Downs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'QLD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Warrego'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SA'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Giles'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SA'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Grey'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SA'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Stuart'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'WA'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Durack'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'WA'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Geraldton'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'WA'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Kalgoorlie'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'WA'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Moore'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'WA'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'North West Central'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-9" name="rest_code_942574733e6f4f4c973987c88301bea4-9"&gt;&lt;/a&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-10" name="rest_code_942574733e6f4f4c973987c88301bea4-10"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tfy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-11" name="rest_code_942574733e6f4f4c973987c88301bea4-11"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-12" name="rest_code_942574733e6f4f4c973987c88301bea4-12"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;"yes"&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-13" name="rest_code_942574733e6f4f4c973987c88301bea4-13"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-14" name="rest_code_942574733e6f4f4c973987c88301bea4-14"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-15" name="rest_code_942574733e6f4f4c973987c88301bea4-15"&gt;&lt;/a&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-16" name="rest_code_942574733e6f4f4c973987c88301bea4-16"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fedstate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-17" name="rest_code_942574733e6f4f4c973987c88301bea4-17"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;fmtstr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"""&lt;/span&gt;&lt;span class="si"&gt;{statename:6}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;{electorate:30}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;{federal}&lt;/span&gt;&lt;span class="s2"&gt;"""&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-18" name="rest_code_942574733e6f4f4c973987c88301bea4-18"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmtstr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;electorate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;federal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tfy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])))&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-19" name="rest_code_942574733e6f4f4c973987c88301bea4-19"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-20" name="rest_code_942574733e6f4f4c973987c88301bea4-20"&gt;&lt;/a&gt;&lt;span class="n"&gt;NSW&lt;/span&gt;    &lt;span class="n"&gt;Ballina&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-21" name="rest_code_942574733e6f4f4c973987c88301bea4-21"&gt;&lt;/a&gt;&lt;span class="n"&gt;NSW&lt;/span&gt;    &lt;span class="n"&gt;Barwon&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-22" name="rest_code_942574733e6f4f4c973987c88301bea4-22"&gt;&lt;/a&gt;&lt;span class="n"&gt;NSW&lt;/span&gt;    &lt;span class="n"&gt;Clarence&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-23" name="rest_code_942574733e6f4f4c973987c88301bea4-23"&gt;&lt;/a&gt;&lt;span class="n"&gt;NSW&lt;/span&gt;    &lt;span class="n"&gt;Lismore&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-24" name="rest_code_942574733e6f4f4c973987c88301bea4-24"&gt;&lt;/a&gt;&lt;span class="n"&gt;NSW&lt;/span&gt;    &lt;span class="n"&gt;New&lt;/span&gt; &lt;span class="n"&gt;England&lt;/span&gt;                    &lt;span class="n"&gt;yes&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-25" name="rest_code_942574733e6f4f4c973987c88301bea4-25"&gt;&lt;/a&gt;&lt;span class="n"&gt;NSW&lt;/span&gt;    &lt;span class="n"&gt;Northern&lt;/span&gt; &lt;span class="n"&gt;Tablelands&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-26" name="rest_code_942574733e6f4f4c973987c88301bea4-26"&gt;&lt;/a&gt;&lt;span class="n"&gt;NSW&lt;/span&gt;    &lt;span class="n"&gt;Page&lt;/span&gt;                           &lt;span class="n"&gt;yes&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-27" name="rest_code_942574733e6f4f4c973987c88301bea4-27"&gt;&lt;/a&gt;&lt;span class="n"&gt;NSW&lt;/span&gt;    &lt;span class="n"&gt;Parkes&lt;/span&gt;                         &lt;span class="n"&gt;yes&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-28" name="rest_code_942574733e6f4f4c973987c88301bea4-28"&gt;&lt;/a&gt;&lt;span class="n"&gt;QLD&lt;/span&gt;    &lt;span class="n"&gt;Maranoa&lt;/span&gt;                        &lt;span class="n"&gt;yes&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-29" name="rest_code_942574733e6f4f4c973987c88301bea4-29"&gt;&lt;/a&gt;&lt;span class="n"&gt;QLD&lt;/span&gt;    &lt;span class="n"&gt;Southern&lt;/span&gt; &lt;span class="n"&gt;Downs&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-30" name="rest_code_942574733e6f4f4c973987c88301bea4-30"&gt;&lt;/a&gt;&lt;span class="n"&gt;QLD&lt;/span&gt;    &lt;span class="n"&gt;Warrego&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-31" name="rest_code_942574733e6f4f4c973987c88301bea4-31"&gt;&lt;/a&gt;&lt;span class="n"&gt;SA&lt;/span&gt;     &lt;span class="n"&gt;Giles&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-32" name="rest_code_942574733e6f4f4c973987c88301bea4-32"&gt;&lt;/a&gt;&lt;span class="n"&gt;SA&lt;/span&gt;     &lt;span class="n"&gt;Grey&lt;/span&gt;                           &lt;span class="n"&gt;yes&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-33" name="rest_code_942574733e6f4f4c973987c88301bea4-33"&gt;&lt;/a&gt;&lt;span class="n"&gt;SA&lt;/span&gt;     &lt;span class="n"&gt;Stuart&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-34" name="rest_code_942574733e6f4f4c973987c88301bea4-34"&gt;&lt;/a&gt;&lt;span class="n"&gt;WA&lt;/span&gt;     &lt;span class="n"&gt;Durack&lt;/span&gt;                         &lt;span class="n"&gt;yes&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-35" name="rest_code_942574733e6f4f4c973987c88301bea4-35"&gt;&lt;/a&gt;&lt;span class="n"&gt;WA&lt;/span&gt;     &lt;span class="n"&gt;Geraldton&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-36" name="rest_code_942574733e6f4f4c973987c88301bea4-36"&gt;&lt;/a&gt;&lt;span class="n"&gt;WA&lt;/span&gt;     &lt;span class="n"&gt;Kalgoorlie&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-37" name="rest_code_942574733e6f4f4c973987c88301bea4-37"&gt;&lt;/a&gt;&lt;span class="n"&gt;WA&lt;/span&gt;     &lt;span class="n"&gt;Moore&lt;/span&gt;
&lt;a id="rest_code_942574733e6f4f4c973987c88301bea4-38" name="rest_code_942574733e6f4f4c973987c88301bea4-38"&gt;&lt;/a&gt;&lt;span class="n"&gt;WA&lt;/span&gt;     &lt;span class="n"&gt;North&lt;/span&gt; &lt;span class="n"&gt;West&lt;/span&gt; &lt;span class="n"&gt;Central&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;I could have added a &lt;cite&gt;WHERE E.FEDERAL = TRUE&lt;/cite&gt; to the query, too.&lt;/p&gt;
&lt;p&gt;Finally, let's see what state electorates in WA and SA are on the border:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_2beae5225a294a6fa13c6369b79d1f66-1" name="rest_code_2beae5225a294a6fa13c6369b79d1f66-1"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"""SELECT STATENAME, ELECTORATE FROM ELECTORATES E&lt;/span&gt;
&lt;a id="rest_code_2beae5225a294a6fa13c6369b79d1f66-2" name="rest_code_2beae5225a294a6fa13c6369b79d1f66-2"&gt;&lt;/a&gt;&lt;span class="s2"&gt;...                   WHERE ELECTORATE_PK IN&lt;/span&gt;
&lt;a id="rest_code_2beae5225a294a6fa13c6369b79d1f66-3" name="rest_code_2beae5225a294a6fa13c6369b79d1f66-3"&gt;&lt;/a&gt;&lt;span class="s2"&gt;...                       (SELECT DISTINCT ELECTORATE_FK FROM GEOPOINTS WHERE LATITUDE BETWEEN -60.00 AND -25.995&lt;/span&gt;
&lt;a id="rest_code_2beae5225a294a6fa13c6369b79d1f66-4" name="rest_code_2beae5225a294a6fa13c6369b79d1f66-4"&gt;&lt;/a&gt;&lt;span class="s2"&gt;                           AND LONGITUDE BETWEEN 128.995 AND 129.1)&lt;/span&gt;
&lt;a id="rest_code_2beae5225a294a6fa13c6369b79d1f66-5" name="rest_code_2beae5225a294a6fa13c6369b79d1f66-5"&gt;&lt;/a&gt;&lt;span class="s2"&gt;...                   AND FEDERAL = FALSE ORDER BY STATENAME, ELECTORATE"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_2beae5225a294a6fa13c6369b79d1f66-6" name="rest_code_2beae5225a294a6fa13c6369b79d1f66-6"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a id="rest_code_2beae5225a294a6fa13c6369b79d1f66-7" name="rest_code_2beae5225a294a6fa13c6369b79d1f66-7"&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_res&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_2beae5225a294a6fa13c6369b79d1f66-8" name="rest_code_2beae5225a294a6fa13c6369b79d1f66-8"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"""&lt;/span&gt;&lt;span class="si"&gt;{statename:6}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;{electorate}&lt;/span&gt;&lt;span class="s2"&gt;"""&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;_res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;electorate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;_res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;a id="rest_code_2beae5225a294a6fa13c6369b79d1f66-9" name="rest_code_2beae5225a294a6fa13c6369b79d1f66-9"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;a id="rest_code_2beae5225a294a6fa13c6369b79d1f66-10" name="rest_code_2beae5225a294a6fa13c6369b79d1f66-10"&gt;&lt;/a&gt;&lt;span class="n"&gt;NT&lt;/span&gt;     &lt;span class="n"&gt;Namatjira&lt;/span&gt;
&lt;a id="rest_code_2beae5225a294a6fa13c6369b79d1f66-11" name="rest_code_2beae5225a294a6fa13c6369b79d1f66-11"&gt;&lt;/a&gt;&lt;span class="n"&gt;SA&lt;/span&gt;     &lt;span class="n"&gt;Giles&lt;/span&gt;
&lt;a id="rest_code_2beae5225a294a6fa13c6369b79d1f66-12" name="rest_code_2beae5225a294a6fa13c6369b79d1f66-12"&gt;&lt;/a&gt;&lt;span class="n"&gt;WA&lt;/span&gt;     &lt;span class="n"&gt;Kalgoorlie&lt;/span&gt;
&lt;a id="rest_code_2beae5225a294a6fa13c6369b79d1f66-13" name="rest_code_2beae5225a294a6fa13c6369b79d1f66-13"&gt;&lt;/a&gt;&lt;span class="n"&gt;WA&lt;/span&gt;     &lt;span class="n"&gt;North&lt;/span&gt; &lt;span class="n"&gt;West&lt;/span&gt; &lt;span class="n"&gt;Central&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Why do we have that electorate from the Northern Territory? It's because the coordinates are a bit fuzzy and we had to use a range (the BETWEEN) in our query.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="finally"&gt;
&lt;h2&gt;Finally&lt;/h2&gt;
&lt;p&gt;I apologise, because while this was supposed to be a &lt;em&gt;brief&lt;/em&gt; introduction I did get side-tracked with data investigation.
I suppose that's an occupational hazard since I'm a Data Engineer working for a company which provides GIS-related services.
Anyway, in terms of depth, this was definitely only scratching the surface of what is possible with Python and databases. I
encourage you to go and read the &lt;a class="reference external" href="https://www.python.org/dev/peps/pep-0249/"&gt;Python Database API&lt;/a&gt; as well as the SQL reference manual(s) for your chosen database and
its binding documentation.&lt;/p&gt;
&lt;!-- put references after this point --&gt;
&lt;/section&gt;</description><category>cx_Oracle</category><category>Data Engineering</category><category>Data Investigation</category><category>Database</category><category>Databases</category><category>Oracle</category><category>PEP 249</category><category>PostgreSQL</category><category>programming</category><category>psycopg2</category><category>Python</category><category>software engineering</category><category>SQL</category><guid>https://www.jmcpdotcom.com/blog/posts/2020-06-03-a-brief-introduction-to-the-python-database-api/</guid><pubDate>Tue, 02 Jun 2020 23:37:00 GMT</pubDate></item><item><title>init(Apache Spark)</title><link>https://www.jmcpdotcom.com/blog/posts/2019-10-11-apache-spark-init/</link><dc:creator>jmcp</dc:creator><description>&lt;p&gt;In a &lt;a class="reference external" href="https://www.jmcpdotcom.com/blog/posts/2019-10-04-microservices-part-2/"&gt;previous post&lt;/a&gt; I wrote about how I've started down the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Data_science"&gt;Data Science&lt;/a&gt;
path, kicking off with an exploration of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Sentiment_analysis"&gt;sentiment analysis&lt;/a&gt; for political
tweets. This is a topic which I will come back to in the future, not least
because the &lt;a class="reference external" href="http://www.nltk.org"&gt;nltk&lt;/a&gt;  &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Text_corpus"&gt;corpus&lt;/a&gt; I've made use of for &lt;a class="reference external" href="https://github.com/jmcp/au-pol-sentiment"&gt;au-pol-sentiment&lt;/a&gt; is
based on &lt;em&gt;British&lt;/em&gt; political tweets. While Australia and Britain share a
common political heritage, I'm not completely confident that &lt;em&gt;our&lt;/em&gt; political
discourse is quite covered by &lt;a class="reference external" href="https://www.nltk.org/howto/twitter.html"&gt;that corpus&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the meantime, another aspect of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Data_science"&gt;Data Science&lt;/a&gt; in practice is the use of
an ecosystem called &lt;a class="reference external" href="https://spark.apache.org"&gt;Apache Spark&lt;/a&gt;. Leaving aside my 20+ years of muscle
memory spelling it as &lt;a class="reference external" href="https://sparc.org"&gt;SPARC&lt;/a&gt;, this is a &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Machine_learning"&gt;Machine Learning&lt;/a&gt; engine,
described on the homepage as &lt;strong&gt;a unified analytics engine for large-scale data
processing&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;My experience is that when I want or need to learn a new toolkit or utility,
the best way to do so is by trying to directly solve a specific problem with
it. One problem (ok, not really a problem, more a set of questions) I have is
that with all of the data I've gathered since 2013 from my &lt;a class="reference external" href="https://www.jmcpdotcom.com/blog/posts/2018-04-03-monitoring-my-inverter/"&gt;solar inverter&lt;/a&gt;
I'm dependent on &lt;a class="reference external" href="https://pvoutput.org"&gt;pvoutput.org&lt;/a&gt; for finding per-year and per-month
averages, maxima and minima. So with a &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Data_science"&gt;data science&lt;/a&gt;-focused job
interview (with &lt;a class="reference external" href="http://labs.oracle.com"&gt;Oracle Labs&lt;/a&gt;) approaching, I decided to get stuck in
and get started with &lt;a class="reference external" href="https://spark.apache.org"&gt;Apache Spark&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The first issue I faced was implementing the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Extract,_transform,_load"&gt;ETL&lt;/a&gt; pipeline. I have two
types of files to load - the first contains the output from
&lt;a class="reference external" href="https://github.com/jcroucher/solarmonj"&gt;solarmonj&lt;/a&gt;, the second has the output from my &lt;a class="reference external" href="https://www.jmcpdotcom.com/blog/posts/2018-04-03-monitoring-my-inverter/"&gt;solar inverter&lt;/a&gt;
script.&lt;/p&gt;
&lt;p&gt;Here's the first schema form:&lt;/p&gt;
&lt;table&gt;
&lt;colgroup&gt;
&lt;col style="width: 42%"&gt;
&lt;col style="width: 58%"&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th class="head"&gt;&lt;p&gt;Field name&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;Units&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;Timestamp&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;seconds-since-epoch&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;Temperature&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;float (degrees C)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;energyNow&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;float (Watts)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;energyToday&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;float (Watt-hours)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;powerGenerated&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;float (Hertz)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;voltageDC&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;float (Volts)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;current&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;float (Amps)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;energyTotal&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;float (Watt-hours)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;voltageAC&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;float (Volts)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;The second schema is from jfy-monitor, and has this schema:&lt;/p&gt;
&lt;table&gt;
&lt;colgroup&gt;
&lt;col style="width: 31%"&gt;
&lt;col style="width: 69%"&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th class="head"&gt;&lt;p&gt;Field name&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;Units&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;Timestamp&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;ISO8601-like ("yyyy-MM-dd'T'HH:mm:ss")&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;Temperature&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;float (degrees C)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;PowerGenerated&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;float (Watts)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;VoltageDC&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;float (Volts)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;Current&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;float (Amps)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;EnergyGenerated&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;float (Watts-Hours)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;VoltageAC&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;float (Volts)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;There are two other salient pieces of information about these files. The
first is that the &lt;em&gt;energyTotal&lt;/em&gt; and &lt;em&gt;EnergyGenerated&lt;/em&gt; fields are running
totals of the amount of energy generated on that particular day. The
second is that in the first version of the schema, &lt;em&gt;energyTotal&lt;/em&gt; needs
to be multiplied by 1000 to get the actual KW/h value.&lt;/p&gt;
&lt;p&gt;With that knowledge ready, let's dive into the code.&lt;/p&gt;
&lt;p&gt;The first step is to start up a &lt;a class="reference external" href="https://dzone.com/articles/introduction-to-spark-session"&gt;Spark&lt;/a&gt; session:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_b924a62ab4234a699a0c981baaffe174-1" name="rest_code_b924a62ab4234a699a0c981baaffe174-1"&gt;&lt;/a&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyspark.sql.functions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;date_format&lt;/span&gt;
&lt;a id="rest_code_b924a62ab4234a699a0c981baaffe174-2" name="rest_code_b924a62ab4234a699a0c981baaffe174-2"&gt;&lt;/a&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyspark&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SparkContext&lt;/span&gt;
&lt;a id="rest_code_b924a62ab4234a699a0c981baaffe174-3" name="rest_code_b924a62ab4234a699a0c981baaffe174-3"&gt;&lt;/a&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyspark.sql&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SparkSession&lt;/span&gt;
&lt;a id="rest_code_b924a62ab4234a699a0c981baaffe174-4" name="rest_code_b924a62ab4234a699a0c981baaffe174-4"&gt;&lt;/a&gt;
&lt;a id="rest_code_b924a62ab4234a699a0c981baaffe174-5" name="rest_code_b924a62ab4234a699a0c981baaffe174-5"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Basic Spark session configuration&lt;/span&gt;
&lt;a id="rest_code_b924a62ab4234a699a0c981baaffe174-6" name="rest_code_b924a62ab4234a699a0c981baaffe174-6"&gt;&lt;/a&gt;&lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SparkContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"local"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"PV Inverter Analysis"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_b924a62ab4234a699a0c981baaffe174-7" name="rest_code_b924a62ab4234a699a0c981baaffe174-7"&gt;&lt;/a&gt;&lt;span class="n"&gt;spark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SparkSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_b924a62ab4234a699a0c981baaffe174-8" name="rest_code_b924a62ab4234a699a0c981baaffe174-8"&gt;&lt;/a&gt;
&lt;a id="rest_code_b924a62ab4234a699a0c981baaffe174-9" name="rest_code_b924a62ab4234a699a0c981baaffe174-9"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# We don't need most of this output&lt;/span&gt;
&lt;a id="rest_code_b924a62ab4234a699a0c981baaffe174-10" name="rest_code_b924a62ab4234a699a0c981baaffe174-10"&gt;&lt;/a&gt;&lt;span class="n"&gt;log4j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_jvm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log4j&lt;/span&gt;
&lt;a id="rest_code_b924a62ab4234a699a0c981baaffe174-11" name="rest_code_b924a62ab4234a699a0c981baaffe174-11"&gt;&lt;/a&gt;&lt;span class="n"&gt;log4j&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LogManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getRootLogger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log4j&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Level&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;I observed while prototyping this in the &lt;a class="reference external" href="https://github.com/apache/spark/tree/master/python"&gt;pyspark&lt;/a&gt;  &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop"&gt;REPL&lt;/a&gt; environment
that if I didn't turn the logging output right down, then I'd see
squillions of &lt;strong&gt;INFO&lt;/strong&gt; messages.&lt;/p&gt;
&lt;p&gt;The second step is to generate a list of files. As you might have
guessed, I've got a year/month/day hierarchy - but the older files have
a &lt;em&gt;csv&lt;/em&gt; extension. To get those files (and since I want to be able to
process any given year &lt;em&gt;or&lt;/em&gt; year+month combination), I need to use some
globbing:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-1" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-1"&gt;&lt;/a&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;glob&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-2" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-2"&gt;&lt;/a&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-3" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-3"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generateFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topdir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-4" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-4"&gt;&lt;/a&gt;    &lt;span class="sd"&gt;"""Construct per-year dicts of lists of files"""&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-5" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-5"&gt;&lt;/a&gt;    &lt;span class="n"&gt;allfiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-6" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-6"&gt;&lt;/a&gt;    &lt;span class="n"&gt;kkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-7" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-7"&gt;&lt;/a&gt;    &lt;span class="n"&gt;patterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-8" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-8"&gt;&lt;/a&gt;    &lt;span class="n"&gt;months&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-9" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-9"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# Since some of our data dirs have months as bare numbers and&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-10" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-10"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# others have a prepended 0, let's match them correctly.&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-11" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-11"&gt;&lt;/a&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-12" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-12"&gt;&lt;/a&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-13" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-13"&gt;&lt;/a&gt;            &lt;span class="n"&gt;months&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-14" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-14"&gt;&lt;/a&gt;        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-15" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-15"&gt;&lt;/a&gt;            &lt;span class="n"&gt;months&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-16" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-16"&gt;&lt;/a&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-17" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-17"&gt;&lt;/a&gt;        &lt;span class="n"&gt;patterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{year}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{monthp}&lt;/span&gt;&lt;span class="s2"&gt;/**"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;monthp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;monthp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-18" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-18"&gt;&lt;/a&gt;                    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;monthp&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;months&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-19" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-19"&gt;&lt;/a&gt;        &lt;span class="n"&gt;kkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-20" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-20"&gt;&lt;/a&gt;    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-21" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-21"&gt;&lt;/a&gt;        &lt;span class="n"&gt;patterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{year}&lt;/span&gt;&lt;span class="s2"&gt;/*/**"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-22" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-22"&gt;&lt;/a&gt;        &lt;span class="n"&gt;kkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-23" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-23"&gt;&lt;/a&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-24" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-24"&gt;&lt;/a&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;patterns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-25" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-25"&gt;&lt;/a&gt;        &lt;span class="n"&gt;globs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-26" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-26"&gt;&lt;/a&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pat&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;patterns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-27" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-27"&gt;&lt;/a&gt;            &lt;span class="n"&gt;globs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topdir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pat&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-28" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-28"&gt;&lt;/a&gt;        &lt;span class="n"&gt;allfiles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;kkey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;globs&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-29" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-29"&gt;&lt;/a&gt;    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-30" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-30"&gt;&lt;/a&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;yy&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;allYears&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-31" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-31"&gt;&lt;/a&gt;            &lt;span class="n"&gt;allfiles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;yy&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topdir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-32" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-32"&gt;&lt;/a&gt;                                                  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{yy}&lt;/span&gt;&lt;span class="s2"&gt;/*/*"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;yy&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;a id="rest_code_7f3927fc441f4675a89b54b16e45c4dd-33" name="rest_code_7f3927fc441f4675a89b54b16e45c4dd-33"&gt;&lt;/a&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;allfiles&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;To load in each file, I turned to the tried-and-true &lt;a class="reference external" href="https://www.python.org"&gt;Python&lt;/a&gt; standard
module &lt;strong&gt;csv&lt;/strong&gt;, and - rather than having a v1 and v2 processing
function, I model &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself"&gt;DRY&lt;/a&gt; and use an input argument to determine which
set of elements to match:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-1" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-1"&gt;&lt;/a&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;csv&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-2" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-2"&gt;&lt;/a&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-3" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-3"&gt;&lt;/a&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-4" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-4"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;importCSV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isOld&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-5" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-5"&gt;&lt;/a&gt;    &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-6" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-6"&gt;&lt;/a&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;isOld&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-7" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-7"&gt;&lt;/a&gt;        &lt;span class="n"&gt;multiplier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1000.0&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-8" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-8"&gt;&lt;/a&gt;    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-9" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-9"&gt;&lt;/a&gt;        &lt;span class="n"&gt;multiplier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-10" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-10"&gt;&lt;/a&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-11" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-11"&gt;&lt;/a&gt;    &lt;span class="n"&gt;csvreader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readlines&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-12" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-12"&gt;&lt;/a&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;csvreader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-13" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-13"&gt;&lt;/a&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-14" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-14"&gt;&lt;/a&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;isOld&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-15" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-15"&gt;&lt;/a&gt;                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tstamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_enow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_etoday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;powergen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vdc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-16" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-16"&gt;&lt;/a&gt;                 &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;energen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vac&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-17" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-17"&gt;&lt;/a&gt;            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-18" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-18"&gt;&lt;/a&gt;                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tstamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;powergen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vdc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;energen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vac&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-19" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-19"&gt;&lt;/a&gt;        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;_ve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-20" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-20"&gt;&lt;/a&gt;            &lt;span class="c1"&gt;# print("failed at {row} of {fname}".format(row=row, fname=fname))&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-21" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-21"&gt;&lt;/a&gt;            &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-22" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-22"&gt;&lt;/a&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-23" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-23"&gt;&lt;/a&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s2"&gt;"e"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-24" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-24"&gt;&lt;/a&gt;            &lt;span class="c1"&gt;# invalid line, skip it&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-25" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-25"&gt;&lt;/a&gt;            &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-26" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-26"&gt;&lt;/a&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-27" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-27"&gt;&lt;/a&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;isOld&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-28" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-28"&gt;&lt;/a&gt;            &lt;span class="n"&gt;isostamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromtimestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tstamp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-29" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-29"&gt;&lt;/a&gt;        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-30" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-30"&gt;&lt;/a&gt;            &lt;span class="n"&gt;isostamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tstamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-31" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-31"&gt;&lt;/a&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-32" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-32"&gt;&lt;/a&gt;        &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-33" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-33"&gt;&lt;/a&gt;            &lt;span class="s2"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;isostamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-34" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-34"&gt;&lt;/a&gt;            &lt;span class="s2"&gt;"Temperature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-35" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-35"&gt;&lt;/a&gt;            &lt;span class="s2"&gt;"PowerGenerated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;powergen&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-36" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-36"&gt;&lt;/a&gt;            &lt;span class="s2"&gt;"VoltageDC"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vdc&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-37" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-37"&gt;&lt;/a&gt;            &lt;span class="s2"&gt;"Current"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-38" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-38"&gt;&lt;/a&gt;            &lt;span class="s2"&gt;"EnergyGenerated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;energen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-39" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-39"&gt;&lt;/a&gt;            &lt;span class="s2"&gt;"VoltageAC"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vac&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
&lt;a id="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-40" name="rest_code_0d69783936314703b8e6f5e0b9d0cdaf-40"&gt;&lt;/a&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Now we get to the &lt;a class="reference external" href="https://spark.apache.org"&gt;Apache Spark&lt;/a&gt; part. Having got a dictionary of
anonymous &lt;code class="docutils literal"&gt;dicts&lt;/code&gt; I can turn them into an &lt;code class="docutils literal"&gt;Resilient Distributed
Dataset&lt;/code&gt; (&lt;a class="reference external" href="https://spark.apache.org/docs/latest/rdd-programming-guide.html"&gt;RDD&lt;/a&gt;) and thence a &lt;a class="reference external" href="https://spark.apache.org/docs/latest/sql-programming-guide.html"&gt;DataFrame&lt;/a&gt;. I chose the &lt;a class="reference external" href="https://spark.apache.org/docs/latest/sql-programming-guide.html"&gt;DataFrame&lt;/a&gt;
model rather than a &lt;code class="docutils literal"&gt;Row&lt;/code&gt; because that matches up nicely with my
existing data format. For other applications (such as when I extend my
&lt;a class="reference external" href="https://www.jmcpdotcom.com/blog/posts/2019-10-04-microservices-part-2/"&gt;Twitter Sentiment Analysis&lt;/a&gt; project with the streaming API) I'll use
the &lt;code class="docutils literal"&gt;Row&lt;/code&gt; datatype instead.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-1" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-2" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-2"&gt;&lt;/a&gt;    &lt;span class="sd"&gt;""" Returns an ISO8601-formatted (without microseconds) timestamp"""&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-3" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-3"&gt;&lt;/a&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"%Y-%M-&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;T%H:%m:%S"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-4" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-4"&gt;&lt;/a&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-5" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-5"&gt;&lt;/a&gt;&lt;span class="n"&gt;allFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;generateFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qyear&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qmonth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-6" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-6"&gt;&lt;/a&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-7" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-7"&gt;&lt;/a&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s2"&gt;"Importing data files"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-8" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-8"&gt;&lt;/a&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-9" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-9"&gt;&lt;/a&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;allFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-10" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-10"&gt;&lt;/a&gt;    &lt;span class="n"&gt;rddyear&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-11" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-11"&gt;&lt;/a&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;allFiles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-12" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-12"&gt;&lt;/a&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;".csv"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-13" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-13"&gt;&lt;/a&gt;            &lt;span class="n"&gt;rddyear&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;importCSV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-14" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-14"&gt;&lt;/a&gt;        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-15" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-15"&gt;&lt;/a&gt;            &lt;span class="n"&gt;rddyear&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;importCSV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-16" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-16"&gt;&lt;/a&gt;    &lt;span class="n"&gt;rdds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rddyear&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-17" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-17"&gt;&lt;/a&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-18" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-18"&gt;&lt;/a&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s2"&gt;"All data files imported"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-19" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-19"&gt;&lt;/a&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-20" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-20"&gt;&lt;/a&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;allYears&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-21" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-21"&gt;&lt;/a&gt;    &lt;span class="n"&gt;rdd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parallelize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rdds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-22" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-22"&gt;&lt;/a&gt;    &lt;span class="n"&gt;allFrames&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rdd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toDF&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-23" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-23"&gt;&lt;/a&gt;    &lt;span class="n"&gt;newFrame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"new"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-24" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-24"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# Extend the schema for our convenience&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-25" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-25"&gt;&lt;/a&gt;    &lt;span class="n"&gt;allFrames&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;newFrame&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;allFrames&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-26" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-26"&gt;&lt;/a&gt;        &lt;span class="s2"&gt;"DateOnly"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'timestamp'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"yyyyMMdd"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-27" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-27"&gt;&lt;/a&gt;    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"TimeOnly"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'timestamp'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"HHmmss"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-28" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-28"&gt;&lt;/a&gt;    &lt;span class="n"&gt;allFrames&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;newFrame&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;createOrReplaceTempView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"view&lt;/span&gt;&lt;span class="si"&gt;{year}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-29" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-29"&gt;&lt;/a&gt;        &lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-30" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-30"&gt;&lt;/a&gt;
&lt;a id="rest_code_41e655eadba544f7ac0743ad83a6b51f-31" name="rest_code_41e655eadba544f7ac0743ad83a6b51f-31"&gt;&lt;/a&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s2"&gt;"Data transformed from RDDs into DataFrames"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;The reason I chose to extend the frames with two extra columns is
because when I search for the record dates (minimum and maximum), I want
to have a quick &lt;code class="docutils literal"&gt;SELECT&lt;/code&gt; which I can aggregate on.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_75175a7121f9424494f095f110692f1b-1" name="rest_code_75175a7121f9424494f095f110692f1b-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;ymdquery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SELECT DISTINCT DateOnly from &lt;/span&gt;&lt;span class="si"&gt;{view}&lt;/span&gt;&lt;span class="s2"&gt; WHERE DateOnly "&lt;/span&gt;
&lt;a id="rest_code_75175a7121f9424494f095f110692f1b-2" name="rest_code_75175a7121f9424494f095f110692f1b-2"&gt;&lt;/a&gt;&lt;span class="n"&gt;ymdquery&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"LIKE '&lt;/span&gt;&lt;span class="si"&gt;{yyyymm}&lt;/span&gt;&lt;span class="s2"&gt;%' ORDER BY DateOnly ASC"&lt;/span&gt;
&lt;a id="rest_code_75175a7121f9424494f095f110692f1b-3" name="rest_code_75175a7121f9424494f095f110692f1b-3"&gt;&lt;/a&gt;
&lt;a id="rest_code_75175a7121f9424494f095f110692f1b-4" name="rest_code_75175a7121f9424494f095f110692f1b-4"&gt;&lt;/a&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;allYears&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_75175a7121f9424494f095f110692f1b-5" name="rest_code_75175a7121f9424494f095f110692f1b-5"&gt;&lt;/a&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;mon&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;allMonths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_75175a7121f9424494f095f110692f1b-6" name="rest_code_75175a7121f9424494f095f110692f1b-6"&gt;&lt;/a&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mon&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_75175a7121f9424494f095f110692f1b-7" name="rest_code_75175a7121f9424494f095f110692f1b-7"&gt;&lt;/a&gt;            &lt;span class="n"&gt;yyyymm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_75175a7121f9424494f095f110692f1b-8" name="rest_code_75175a7121f9424494f095f110692f1b-8"&gt;&lt;/a&gt;        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_75175a7121f9424494f095f110692f1b-9" name="rest_code_75175a7121f9424494f095f110692f1b-9"&gt;&lt;/a&gt;            &lt;span class="n"&gt;yyyymm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_75175a7121f9424494f095f110692f1b-10" name="rest_code_75175a7121f9424494f095f110692f1b-10"&gt;&lt;/a&gt;
&lt;a id="rest_code_75175a7121f9424494f095f110692f1b-11" name="rest_code_75175a7121f9424494f095f110692f1b-11"&gt;&lt;/a&gt;        &lt;span class="n"&gt;_dates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ymdquery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_75175a7121f9424494f095f110692f1b-12" name="rest_code_75175a7121f9424494f095f110692f1b-12"&gt;&lt;/a&gt;            &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yyyymm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;yyyymm&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a id="rest_code_75175a7121f9424494f095f110692f1b-13" name="rest_code_75175a7121f9424494f095f110692f1b-13"&gt;&lt;/a&gt;        &lt;span class="n"&gt;days&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;asDict&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s2"&gt;"DateOnly"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_dates&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_75175a7121f9424494f095f110692f1b-14" name="rest_code_75175a7121f9424494f095f110692f1b-14"&gt;&lt;/a&gt;
&lt;a id="rest_code_75175a7121f9424494f095f110692f1b-15" name="rest_code_75175a7121f9424494f095f110692f1b-15"&gt;&lt;/a&gt;        &lt;span class="n"&gt;_monthMax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_75175a7121f9424494f095f110692f1b-16" name="rest_code_75175a7121f9424494f095f110692f1b-16"&gt;&lt;/a&gt;            &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateOnly&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_75175a7121f9424494f095f110692f1b-17" name="rest_code_75175a7121f9424494f095f110692f1b-17"&gt;&lt;/a&gt;                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"EnergyGenerated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_75175a7121f9424494f095f110692f1b-18" name="rest_code_75175a7121f9424494f095f110692f1b-18"&gt;&lt;/a&gt;        &lt;span class="n"&gt;monthMax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_monthMax&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;asDict&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s2"&gt;"max(EnergyGenerated)"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;I keep track of each day's maximum, and update my &lt;code class="docutils literal"&gt;minval&lt;/code&gt; and
&lt;code class="docutils literal"&gt;minDay&lt;/code&gt; as required. All this information is then stored in a
per-month &lt;code class="docutils literal"&gt;dict&lt;/code&gt;, and then in a per-year &lt;code class="docutils literal"&gt;dict&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The last stage is to print out the record dates, monthly and yearly
totals, averages and other values.&lt;/p&gt;
&lt;p&gt;Running this utility on my 4-core &lt;a class="reference external" href="https://www.ubuntu.com"&gt;Ubuntu&lt;/a&gt; system at home, I get what I
believe are &lt;em&gt;ok&lt;/em&gt; timings for whole-year investigations, and &lt;em&gt;reasonable&lt;/em&gt;
timings if I check a specific month.&lt;/p&gt;
&lt;p&gt;When I run the utility for January 2018, the output looks like this:&lt;/p&gt;
&lt;pre class="code shell"&gt;&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-1" name="rest_code_e4b6068f059945448d35a5b59aacd665-1"&gt;&lt;/a&gt;&lt;span class="o"&gt;(&lt;/span&gt;v-3.7-linux&lt;span class="o"&gt;)&lt;/span&gt; flerken:solar-spark $ &lt;span class="nv"&gt;SPARK_LOCAL_IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0.0.0 &lt;span class="nb"&gt;time&lt;/span&gt; -f &lt;span class="s2"&gt;"%E"&lt;/span&gt;  spark-submit --executor-memory 2G --driver-memory 2G solar-spark.py  -y &lt;span class="m"&gt;2018&lt;/span&gt; -m &lt;span class="m"&gt;1&lt;/span&gt;
&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-2" name="rest_code_e4b6068f059945448d35a5b59aacd665-2"&gt;&lt;/a&gt;&lt;span class="m"&gt;19&lt;/span&gt;/10/15 &lt;span class="m"&gt;12&lt;/span&gt;:23:57 WARN NativeCodeLoader: Unable to load native-hadoop library &lt;span class="k"&gt;for&lt;/span&gt; your platform... using builtin-java classes where applicable
&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-3" name="rest_code_e4b6068f059945448d35a5b59aacd665-3"&gt;&lt;/a&gt;Using Spark default log4j profile: org/apache/spark/log4j-defaults.properties
&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-4" name="rest_code_e4b6068f059945448d35a5b59aacd665-4"&gt;&lt;/a&gt;
&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-5" name="rest_code_e4b6068f059945448d35a5b59aacd665-5"&gt;&lt;/a&gt;&lt;span class="o"&gt;[&lt;/span&gt;Most INFO-level output elided&lt;span class="o"&gt;]&lt;/span&gt;
&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-6" name="rest_code_e4b6068f059945448d35a5b59aacd665-6"&gt;&lt;/a&gt;
&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-7" name="rest_code_e4b6068f059945448d35a5b59aacd665-7"&gt;&lt;/a&gt;&lt;span class="m"&gt;19&lt;/span&gt;/10/15 &lt;span class="m"&gt;12&lt;/span&gt;:23:58 INFO Utils: Successfully started service &lt;span class="s1"&gt;'SparkUI'&lt;/span&gt; on port &lt;span class="m"&gt;4040&lt;/span&gt;.
&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-8" name="rest_code_e4b6068f059945448d35a5b59aacd665-8"&gt;&lt;/a&gt;&lt;span class="m"&gt;19&lt;/span&gt;/10/15 &lt;span class="m"&gt;12&lt;/span&gt;:23:58 INFO SparkUI: Bound SparkUI to &lt;span class="m"&gt;0&lt;/span&gt;.0.0.0, and started at http://0.0.0.0:4040
&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-9" name="rest_code_e4b6068f059945448d35a5b59aacd665-9"&gt;&lt;/a&gt;
&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-10" name="rest_code_e4b6068f059945448d35a5b59aacd665-10"&gt;&lt;/a&gt;&lt;span class="m"&gt;2019&lt;/span&gt;-23-15T12:10:59 Importing data files
&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-11" name="rest_code_e4b6068f059945448d35a5b59aacd665-11"&gt;&lt;/a&gt;&lt;span class="m"&gt;2019&lt;/span&gt;-23-15T12:10:59 All data files imported
&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-12" name="rest_code_e4b6068f059945448d35a5b59aacd665-12"&gt;&lt;/a&gt;/space/jmcp/web/v-3.7-linux/lib/python3.7/site-packages/pyspark/python/lib/pyspark.zip/pyspark/sql/session.py:366: UserWarning: Using RDD of dict to inferSchema is deprecated. Use pyspark.sql.Row instead
&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-13" name="rest_code_e4b6068f059945448d35a5b59aacd665-13"&gt;&lt;/a&gt;&lt;span class="m"&gt;2019&lt;/span&gt;-24-15T12:10:01 Data transformed from RDDs into DataFrames
&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-14" name="rest_code_e4b6068f059945448d35a5b59aacd665-14"&gt;&lt;/a&gt;&lt;span class="m"&gt;2019&lt;/span&gt;-24-15T12:10:01 Analysing &lt;span class="m"&gt;2018&lt;/span&gt;
&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-15" name="rest_code_e4b6068f059945448d35a5b59aacd665-15"&gt;&lt;/a&gt;&lt;span class="m"&gt;2019&lt;/span&gt;-24-15T12:10:01          January
&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-16" name="rest_code_e4b6068f059945448d35a5b59aacd665-16"&gt;&lt;/a&gt;&lt;span class="m"&gt;2019&lt;/span&gt;-24-15T12:10:15 All data analysed
&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-17" name="rest_code_e4b6068f059945448d35a5b59aacd665-17"&gt;&lt;/a&gt;&lt;span class="m"&gt;2019&lt;/span&gt;-24-15T12:10:15 &lt;span class="m"&gt;2018&lt;/span&gt; total generation: &lt;span class="m"&gt;436130&lt;/span&gt;.00 KW/h
&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-18" name="rest_code_e4b6068f059945448d35a5b59aacd665-18"&gt;&lt;/a&gt;&lt;span class="m"&gt;2019&lt;/span&gt;-24-15T12:10:15         January total:               &lt;span class="m"&gt;436130&lt;/span&gt;.00 KW/h
&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-19" name="rest_code_e4b6068f059945448d35a5b59aacd665-19"&gt;&lt;/a&gt;&lt;span class="m"&gt;2019&lt;/span&gt;-24-15T12:10:15         Record dates &lt;span class="k"&gt;for&lt;/span&gt; January:    Max on &lt;span class="m"&gt;20180131&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;16780&lt;/span&gt;.00 KW/h&lt;span class="o"&gt;)&lt;/span&gt;, Min on &lt;span class="m"&gt;20180102&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10560&lt;/span&gt;.00 KW/h&lt;span class="o"&gt;)&lt;/span&gt;
&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-20" name="rest_code_e4b6068f059945448d35a5b59aacd665-20"&gt;&lt;/a&gt;&lt;span class="m"&gt;2019&lt;/span&gt;-24-15T12:10:15         Average daily generation  &lt;span class="m"&gt;14068&lt;/span&gt;.71 KW/h
&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-21" name="rest_code_e4b6068f059945448d35a5b59aacd665-21"&gt;&lt;/a&gt;&lt;span class="m"&gt;2019&lt;/span&gt;-24-15T12:10:15 ----------------
&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-22" name="rest_code_e4b6068f059945448d35a5b59aacd665-22"&gt;&lt;/a&gt;
&lt;a id="rest_code_e4b6068f059945448d35a5b59aacd665-23" name="rest_code_e4b6068f059945448d35a5b59aacd665-23"&gt;&lt;/a&gt;&lt;span class="m"&gt;0&lt;/span&gt;:18.76
&lt;/pre&gt;&lt;p&gt;While that processing is going on, you can see a dashboard with useful
information about the application at &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;http://localhost:4040&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;img alt="Executors" src="https://www.jmcpdotcom.com/blog/images/2019/spark-executors.png"&gt;
&lt;img alt="Jobs" src="https://www.jmcpdotcom.com/blog/images/2019/spark-jobs.png"&gt;
&lt;img alt="Details of a query" src="https://www.jmcpdotcom.com/blog/images/2019/spark-query-details.png"&gt;
&lt;p&gt;You can find the code for this project in my &lt;a class="reference external" href="https://github.com/jmcp"&gt;GitHub&lt;/a&gt; repo &lt;a class="reference external" href="https://github.com/jmcp/solar-spark"&gt;solar-spark&lt;/a&gt;.&lt;/p&gt;
&lt;!-- put references after this point --&gt;</description><category>Apache Spark</category><category>CSV</category><category>data mining</category><category>ETL</category><category>Interview prep</category><category>JSON</category><category>Python</category><category>software engineering</category><category>training</category><category>upskilling</category><guid>https://www.jmcpdotcom.com/blog/posts/2019-10-11-apache-spark-init/</guid><pubDate>Thu, 10 Oct 2019 16:00:00 GMT</pubDate></item><item><title>Microservices (part 2)</title><link>https://www.jmcpdotcom.com/blog/posts/2019-10-04-microservices-part-2/</link><dc:creator>jmcp</dc:creator><description>&lt;p&gt;One principle that I work on is that I should always &lt;em&gt;extend the fix&lt;/em&gt; (learnt
via the &lt;a class="reference external" href="https://www.kepner-tregoe.com"&gt;Kepner-Tregoe&lt;/a&gt;  &lt;a class="reference external" href="https://www.kepner-tregoe.com/training-workshops/our-training-workshops/analytic-trouble-shooting/"&gt;Analytical Troubleshooting&lt;/a&gt; training many years
ago). Following my investigation of how to provide a more accessible method of
&lt;a class="reference external" href="https://www.jmcpdotcom.com/blog/posts/2019-09-27-microservices-part-1/"&gt;determining your electorate&lt;/a&gt;, I came back to the political polling ideas and
got to thinking about how we can track the temperature of a conversation in,
for example &lt;a class="reference external" href="https://twitter.com/search?q=%23auspol&amp;amp;src=typed_query&amp;amp;f=live"&gt;#auspol&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The term for this is &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Sentiment_analysis"&gt;sentiment analysis&lt;/a&gt; and while the major cloud providers
have their own implementations of this (&lt;a class="reference external" href="https://azure.microsoft.com/en-us/services/cognitive-services/text-analytics/"&gt;Microsoft Azure Text Analytics&lt;/a&gt;,
&lt;a class="reference external" href="https://aws.amazon.com/comprehend"&gt;AWS Comprehend&lt;/a&gt;, &lt;a class="reference external" href="https://console.developers.google.com/apis/library/language.googleapis.com"&gt;Google Cloud Natural Language API&lt;/a&gt;) you can also use
&lt;a class="reference external" href="https://www.python.org"&gt;Python&lt;/a&gt;'s &lt;a class="reference external" href="http://www.nltk.org"&gt;nltk&lt;/a&gt; in the comfort of your own venv. It's cheaper, too!&lt;/p&gt;
&lt;p&gt;A bit of searching lead me to &lt;a class="reference external" href="https://twitter.com/chapagain"&gt;@Chapagain&lt;/a&gt;'s &lt;a class="reference external" href="http://blog.chapagain.com.np/python-nltk-twitter-sentiment-analysis-natural-language-processing-nlp/"&gt;post&lt;/a&gt; which was very useful and
got me started - thankyou&lt;/p&gt;
&lt;p&gt;I decided that I really want to do something more real-time, and while I could
have done more scraping with &lt;a class="reference external" href="https://www.crummy.com/software/BeautifulSoup"&gt;Beautiful Soup&lt;/a&gt;, a quick look at the html
that's returned with you run&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_9cc272ee85e3471c9485a47c868598df-1" name="rest_code_9cc272ee85e3471c9485a47c868598df-1"&gt;&lt;/a&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;
&lt;a id="rest_code_9cc272ee85e3471c9485a47c868598df-2" name="rest_code_9cc272ee85e3471c9485a47c868598df-2"&gt;&lt;/a&gt;
&lt;a id="rest_code_9cc272ee85e3471c9485a47c868598df-3" name="rest_code_9cc272ee85e3471c9485a47c868598df-3"&gt;&lt;/a&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://twitter.com/search?q=&lt;/span&gt;&lt;span class="si"&gt;%23a&lt;/span&gt;&lt;span class="s2"&gt;uspol&amp;amp;src=typed_query&amp;amp;f=live"&lt;/span&gt;
&lt;a id="rest_code_9cc272ee85e3471c9485a47c868598df-4" name="rest_code_9cc272ee85e3471c9485a47c868598df-4"&gt;&lt;/a&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_9cc272ee85e3471c9485a47c868598df-5" name="rest_code_9cc272ee85e3471c9485a47c868598df-5"&gt;&lt;/a&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;is eye-wateringly complex. (Go on, try it!) I just couldn't be bothered with
that so I signed up for a &lt;a class="reference external" href="https://twitter.com"&gt;Twitter&lt;/a&gt; developer account and started looking at
the APIs available for &lt;a class="reference external" href="https://developer.twitter.com/en/docs/tweets/search/overview/standard"&gt;searching&lt;/a&gt;. These are easily used with the &lt;a class="reference external" href="https://github.com/ryanmcgrath/twython"&gt;Twython&lt;/a&gt;
library:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_0b15ca5c84c84e01a2743da2c4f34022-1" name="rest_code_0b15ca5c84c84e01a2743da2c4f34022-1"&gt;&lt;/a&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;twython&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Twython&lt;/span&gt;
&lt;a id="rest_code_0b15ca5c84c84e01a2743da2c4f34022-2" name="rest_code_0b15ca5c84c84e01a2743da2c4f34022-2"&gt;&lt;/a&gt;
&lt;a id="rest_code_0b15ca5c84c84e01a2743da2c4f34022-3" name="rest_code_0b15ca5c84c84e01a2743da2c4f34022-3"&gt;&lt;/a&gt;&lt;span class="n"&gt;twitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Tython&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;consumer_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;consumer_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_0b15ca5c84c84e01a2743da2c4f34022-4" name="rest_code_0b15ca5c84c84e01a2743da2c4f34022-4"&gt;&lt;/a&gt;                 &lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;access_token_secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_0b15ca5c84c84e01a2743da2c4f34022-5" name="rest_code_0b15ca5c84c84e01a2743da2c4f34022-5"&gt;&lt;/a&gt;&lt;span class="n"&gt;hashtag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"#auspol"&lt;/span&gt;
&lt;a id="rest_code_0b15ca5c84c84e01a2743da2c4f34022-6" name="rest_code_0b15ca5c84c84e01a2743da2c4f34022-6"&gt;&lt;/a&gt;&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;twitter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;hashtag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"recent"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_0b15ca5c84c84e01a2743da2c4f34022-7" name="rest_code_0b15ca5c84c84e01a2743da2c4f34022-7"&gt;&lt;/a&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tweet&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"statuses"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;a id="rest_code_0b15ca5c84c84e01a2743da2c4f34022-8" name="rest_code_0b15ca5c84c84e01a2743da2c4f34022-8"&gt;&lt;/a&gt;    &lt;span class="n"&gt;sentiment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;classifier_func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;a id="rest_code_0b15ca5c84c84e01a2743da2c4f34022-9" name="rest_code_0b15ca5c84c84e01a2743da2c4f34022-9"&gt;&lt;/a&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sentiment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"pos"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;I hit the rate limit a few times until I realiased that there was a &lt;code class="docutils literal"&gt;while
true&lt;/code&gt; going on inside &lt;a class="reference external" href="https://github.com/ryanmcgrath/twython"&gt;Twython&lt;/a&gt; when using the &lt;code class="docutils literal"&gt;cursor&lt;/code&gt; method. In my
print-to-shell proof of concept, I got past that by using the &lt;code class="docutils literal"&gt;search&lt;/code&gt;
function inside a while loop with a 30second sleep call. I knew that that
wasn't good enough for a web app, and would actually be a road block for doing
a properly updated graph-focused page.&lt;/p&gt;
&lt;p&gt;For that I would need a charting library, and some JavaScript. I started out
using &lt;a class="reference external" href="https://www.chartjs.org"&gt;Chart.js&lt;/a&gt;, but quickly realised that it didn't have any sort of flow,
so then I retooled to use &lt;a class="reference external" href="https://c3js.org/"&gt;C3js&lt;/a&gt; instead.&lt;/p&gt;
&lt;p&gt;The initial render of the template provides the first set of data, which is a
JavaScript array (&lt;code class="docutils literal"&gt;[]&lt;/code&gt;), and checks for a saved hashtag and the id of the
most recently found tweet using &lt;a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage"&gt;Window.sessionStorage()&lt;/a&gt;. Then we set up a
function to get new data when called:&lt;/p&gt;
&lt;pre class="code javascript"&gt;&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-1" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-1"&gt;&lt;/a&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getNewData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-2" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-2"&gt;&lt;/a&gt;    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;xhr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;XMLHttpRequest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-3" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-3"&gt;&lt;/a&gt;    &lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"/sentiment?hashtag="&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;hashtag&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s2"&gt;"&amp;amp;lastid="&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;lastid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-4" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-4"&gt;&lt;/a&gt;    &lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-5" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-5"&gt;&lt;/a&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readyState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mf"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mf"&gt;200&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-6" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-6"&gt;&lt;/a&gt;            &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responseText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-7" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-7"&gt;&lt;/a&gt;            &lt;span class="nx"&gt;sessionStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"lastid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"lastid"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-8" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-8"&gt;&lt;/a&gt;            &lt;span class="nx"&gt;lastid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"lastid"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-9" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-9"&gt;&lt;/a&gt;            &lt;span class="nx"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"labels"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-10" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-10"&gt;&lt;/a&gt;            &lt;span class="c1"&gt;// Did we get new data points?&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-11" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-11"&gt;&lt;/a&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"chartdata"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-12" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-12"&gt;&lt;/a&gt;                &lt;span class="c1"&gt;// yes&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-13" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-13"&gt;&lt;/a&gt;                &lt;span class="nx"&gt;newDataCol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"chartdata"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-14" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-14"&gt;&lt;/a&gt;                &lt;span class="nx"&gt;curidx&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"chartdata"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-15" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-15"&gt;&lt;/a&gt;            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-16" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-16"&gt;&lt;/a&gt;                &lt;span class="nx"&gt;newDataCol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-17" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-17"&gt;&lt;/a&gt;            &lt;span class="p"&gt;}&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-18" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-18"&gt;&lt;/a&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-19" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-19"&gt;&lt;/a&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-20" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-20"&gt;&lt;/a&gt;    &lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-21" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-21"&gt;&lt;/a&gt;        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-22" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-22"&gt;&lt;/a&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-23" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-23"&gt;&lt;/a&gt;    &lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;a id="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-24" name="rest_code_c8cc42dea1bf4af9af4d8a2b68debba4-24"&gt;&lt;/a&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Finally, we need to tell the window to call our &lt;code class="docutils literal"&gt;updateChart()&lt;/code&gt; function
every 30 seconds, and define that function:&lt;/p&gt;
&lt;pre class="code javascript"&gt;&lt;a id="rest_code_3ea794dea366448a8eb960bfc36143d6-1" name="rest_code_3ea794dea366448a8eb960bfc36143d6-1"&gt;&lt;/a&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;updateChart&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_3ea794dea366448a8eb960bfc36143d6-2" name="rest_code_3ea794dea366448a8eb960bfc36143d6-2"&gt;&lt;/a&gt;    &lt;span class="nx"&gt;getNewData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;a id="rest_code_3ea794dea366448a8eb960bfc36143d6-3" name="rest_code_3ea794dea366448a8eb960bfc36143d6-3"&gt;&lt;/a&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newDataCol&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_3ea794dea366448a8eb960bfc36143d6-4" name="rest_code_3ea794dea366448a8eb960bfc36143d6-4"&gt;&lt;/a&gt;        &lt;span class="nx"&gt;chart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;a id="rest_code_3ea794dea366448a8eb960bfc36143d6-5" name="rest_code_3ea794dea366448a8eb960bfc36143d6-5"&gt;&lt;/a&gt;            &lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prevDataCol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_3ea794dea366448a8eb960bfc36143d6-6" name="rest_code_3ea794dea366448a8eb960bfc36143d6-6"&gt;&lt;/a&gt;            &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_3ea794dea366448a8eb960bfc36143d6-7" name="rest_code_3ea794dea366448a8eb960bfc36143d6-7"&gt;&lt;/a&gt;               &lt;span class="nx"&gt;chart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;a id="rest_code_3ea794dea366448a8eb960bfc36143d6-8" name="rest_code_3ea794dea366448a8eb960bfc36143d6-8"&gt;&lt;/a&gt;                   &lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newDataCol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_3ea794dea366448a8eb960bfc36143d6-9" name="rest_code_3ea794dea366448a8eb960bfc36143d6-9"&gt;&lt;/a&gt;                   &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;connectNull&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;a id="rest_code_3ea794dea366448a8eb960bfc36143d6-10" name="rest_code_3ea794dea366448a8eb960bfc36143d6-10"&gt;&lt;/a&gt;               &lt;span class="p"&gt;})&lt;/span&gt;
&lt;a id="rest_code_3ea794dea366448a8eb960bfc36143d6-11" name="rest_code_3ea794dea366448a8eb960bfc36143d6-11"&gt;&lt;/a&gt;            &lt;span class="p"&gt;}&lt;/span&gt;
&lt;a id="rest_code_3ea794dea366448a8eb960bfc36143d6-12" name="rest_code_3ea794dea366448a8eb960bfc36143d6-12"&gt;&lt;/a&gt;        &lt;span class="p"&gt;});&lt;/span&gt;
&lt;a id="rest_code_3ea794dea366448a8eb960bfc36143d6-13" name="rest_code_3ea794dea366448a8eb960bfc36143d6-13"&gt;&lt;/a&gt;        &lt;span class="nx"&gt;prevDataCol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newDataCol&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;a id="rest_code_3ea794dea366448a8eb960bfc36143d6-14" name="rest_code_3ea794dea366448a8eb960bfc36143d6-14"&gt;&lt;/a&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;a id="rest_code_3ea794dea366448a8eb960bfc36143d6-15" name="rest_code_3ea794dea366448a8eb960bfc36143d6-15"&gt;&lt;/a&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;a id="rest_code_3ea794dea366448a8eb960bfc36143d6-16" name="rest_code_3ea794dea366448a8eb960bfc36143d6-16"&gt;&lt;/a&gt;
&lt;a id="rest_code_3ea794dea366448a8eb960bfc36143d6-17" name="rest_code_3ea794dea366448a8eb960bfc36143d6-17"&gt;&lt;/a&gt;&lt;span class="cm"&gt;/* Update the chart every 30 seconds */&lt;/span&gt;
&lt;a id="rest_code_3ea794dea366448a8eb960bfc36143d6-18" name="rest_code_3ea794dea366448a8eb960bfc36143d6-18"&gt;&lt;/a&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updateChart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;So that you can change the hashtag to watch, I added a small &lt;code class="docutils literal"&gt;&amp;lt;form&amp;gt;&lt;/code&gt;
element which POSTs the new hashtag to the &lt;code class="docutils literal"&gt;/sentiment&lt;/code&gt; method on submit and
then re-renders the template.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;img alt="/images/2019/sentiment1.png" src="https://www.jmcpdotcom.com/blog/images/2019/sentiment1.png"&gt;
&lt;p&gt;&lt;br&gt;
&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;p&gt;What I'm particularly happy with is that the JavaScript took me only a few
hours last Saturday morning and was pretty straightforward to write.&lt;/p&gt;
&lt;p&gt;You can find the code for this project in my &lt;a class="reference external" href="https://github.com/jmcp"&gt;GitHub&lt;/a&gt; repo &lt;a class="reference external" href="https://github.com/jmcp/au-pol-sentiment"&gt;au-pol-sentiment&lt;/a&gt;.&lt;/p&gt;
&lt;!-- put references after this point --&gt;
&lt;!--  --&gt;
&lt;!--  --&gt;
&lt;!--  --&gt;</description><category>C3.js</category><category>ETL</category><category>flask</category><category>JSON</category><category>microservices</category><category>nltk</category><category>Python</category><category>sentiment analysis</category><category>software engineering</category><category>training</category><category>twitter</category><category>Twython</category><category>upskilling</category><guid>https://www.jmcpdotcom.com/blog/posts/2019-10-04-microservices-part-2/</guid><pubDate>Thu, 03 Oct 2019 16:00:00 GMT</pubDate></item><item><title>Microservices (part 1)</title><link>https://www.jmcpdotcom.com/blog/posts/2019-09-27-microservices-part-1/</link><dc:creator>jmcp</dc:creator><description>&lt;p&gt;Since I departed from my comfortable niche in &lt;a class="reference external" href="http://www.oracle.com/technetwork/server-storage/solaris11/downloads/index.html"&gt;Solaris&lt;/a&gt; engineering earlier
this year, I've spent a considerable amount of time and energy in re-training
and upskilling to assist my employment prospects. Apart from acquainting
myself with a lot of terminology, I've written code. A lot of code. Mostly, as
it turns out, has been related to &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Microservices"&gt;microservices&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This post is about a &lt;a class="reference external" href="https://github.com/jmcp/find-my-electorate/blob/master/__init__.py"&gt;microservice&lt;/a&gt; I wrote to assist with accessibility in a
specific part of the Australian electoral process (finding out which
electorate you live in) and some supporting digressions.&lt;/p&gt;
&lt;p&gt;You can find all the code for this &lt;a class="reference external" href="https://github.com/jmcp/find-my-electorate/blob/master/__init__.py"&gt;microservice&lt;/a&gt; and its associated data
preparation in my &lt;a class="reference external" href="https://github.com/jmcp"&gt;GitHub&lt;/a&gt; repos &lt;a class="reference external" href="https://github.com/jmcp/grabbag"&gt;grabbag&lt;/a&gt; and &lt;a class="reference external" href="https://github.com/jmcp/find-my-electorate"&gt;find-my-electorate&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On 18 May 2019, Australia had a &lt;a class="reference external" href="https://en.wikipedia.org/wiki/2019_Australian_federal_election"&gt;federal election&lt;/a&gt;, and in the lead up to
that event I became very interested in political polling. While I have a few
ideas on the subject which are on my back burner, mind-mapping the various
components of political polling got to wondering how do the various state,
territory and federal electoral commissions map a voter's address to an
electorate?&lt;/p&gt;
&lt;p&gt;My first point of call was the &lt;a class="reference external" href="https://www.aec.gov.au"&gt;Australian Electoral Commission&lt;/a&gt; and their
&lt;a class="reference external" href="https://electorate.aec.gov.au"&gt;Find my electorate&lt;/a&gt; site. This is nicely laid out and lets you find out
which electorate you are in - by postcode. This is all well and good if you're
in a densely populated area, like the &lt;a class="reference external" href="https://electorate.aec.gov.au/LocalitySearchResults.aspx?filter=4000&amp;amp;filterby=Postcode"&gt;electorate of Brisbane&lt;/a&gt; - three
suburbs. If, however, you choose somewhere else, like &lt;strong&gt;2620&lt;/strong&gt; which covers a
lot of Canberra and surrounding districts, you wind up with several
&lt;a class="reference external" href="https://electorate.aec.gov.au/LocalitySearchResults.aspx?filter=2620&amp;amp;filterby=Postcode"&gt;electorates covering 2620&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://www.aec.gov.au"&gt;AEC&lt;/a&gt;'s website is written in &lt;a class="reference external" href="https://dotnet.microsoft.com/apps/aspnet"&gt;asp.net&lt;/a&gt;, which is up to the task, but
when you have more than one page of results the authors of the page make use
of some (to my mind) squirrelly features and callbacks which make scraping the
site difficult. As best I can determine, the &lt;a class="reference external" href="https://www.aec.gov.au"&gt;AEC&lt;/a&gt; doesn't provide an API to
access this information, so Another Method was required.&lt;/p&gt;
&lt;p&gt;At this point, I turned to the standard libraries for this sort of thing in the
&lt;a class="reference external" href="https://www.python.org"&gt;Python&lt;/a&gt; worldL &lt;a class="reference external" href="https://www.crummy.com/software/BeautifulSoup"&gt;Beautiful Soup&lt;/a&gt; and &lt;a class="reference external" href="https://requests.kennethreitz.org/en/master/"&gt;requests&lt;/a&gt;. I started by setting up a
quick venv to keep the dependencies contained&lt;/p&gt;
&lt;pre class="code shell"&gt;&lt;a id="rest_code_7c27c86dc9fc4fbf8864039fc5ba94f8-1" name="rest_code_7c27c86dc9fc4fbf8864039fc5ba94f8-1"&gt;&lt;/a&gt;$ python3.7 -m venv scraping-venv
&lt;a id="rest_code_7c27c86dc9fc4fbf8864039fc5ba94f8-2" name="rest_code_7c27c86dc9fc4fbf8864039fc5ba94f8-2"&gt;&lt;/a&gt;$ . scraping/bin/activate
&lt;a id="rest_code_7c27c86dc9fc4fbf8864039fc5ba94f8-3" name="rest_code_7c27c86dc9fc4fbf8864039fc5ba94f8-3"&gt;&lt;/a&gt;&lt;span class="o"&gt;(&lt;/span&gt;scraping-venv&lt;span class="o"&gt;)&lt;/span&gt; $ pip install requests bs4 json csv
&lt;/pre&gt;&lt;p&gt;Now since we know the url to GET, we can get the first page of responses very
easily:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_1ea4de6feae84ab498370ae8afaa418b-1" name="rest_code_1ea4de6feae84ab498370ae8afaa418b-1"&gt;&lt;/a&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;
&lt;a id="rest_code_1ea4de6feae84ab498370ae8afaa418b-2" name="rest_code_1ea4de6feae84ab498370ae8afaa418b-2"&gt;&lt;/a&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bs4&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;
&lt;a id="rest_code_1ea4de6feae84ab498370ae8afaa418b-3" name="rest_code_1ea4de6feae84ab498370ae8afaa418b-3"&gt;&lt;/a&gt;
&lt;a id="rest_code_1ea4de6feae84ab498370ae8afaa418b-4" name="rest_code_1ea4de6feae84ab498370ae8afaa418b-4"&gt;&lt;/a&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="s2"&gt;"https://electorate.aec.gov.au/LocalitySearchResults.aspx?"&lt;/span&gt;
&lt;a id="rest_code_1ea4de6feae84ab498370ae8afaa418b-5" name="rest_code_1ea4de6feae84ab498370ae8afaa418b-5"&gt;&lt;/a&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"filter=&lt;/span&gt;&lt;span class="si"&gt;{0}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;filterby=Postcode"&lt;/span&gt;
&lt;a id="rest_code_1ea4de6feae84ab498370ae8afaa418b-6" name="rest_code_1ea4de6feae84ab498370ae8afaa418b-6"&gt;&lt;/a&gt;
&lt;a id="rest_code_1ea4de6feae84ab498370ae8afaa418b-7" name="rest_code_1ea4de6feae84ab498370ae8afaa418b-7"&gt;&lt;/a&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2620&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_1ea4de6feae84ab498370ae8afaa418b-8" name="rest_code_1ea4de6feae84ab498370ae8afaa418b-8"&gt;&lt;/a&gt;&lt;span class="n"&gt;resh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"html.parser"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;&lt;a class="reference external" href="https://www.crummy.com/software/BeautifulSoup"&gt;Beautiful Soup&lt;/a&gt; parses the response text, and gives us a tree-like structure
to work with. Making use of the &lt;a class="reference external" href="https://developers.google.com/web/tools/chrome-devtools/"&gt;Chrome devtools&lt;/a&gt; (or the &lt;a class="reference external" href="https://developer.mozilla.org/en-US/docs/Tools"&gt;Firefox devtools&lt;/a&gt;  )
I could see that I need to find a &lt;code class="docutils literal"&gt;&amp;lt;table&amp;gt;&lt;/code&gt; with an attribute of
&lt;code class="docutils literal"&gt;*ContentPlaceHolderBody_gridViewLocalities*&lt;/code&gt; - what a mouthful! - and then
process all the table rows (&lt;code class="docutils literal"&gt;&amp;lt;tr&amp;gt;&lt;/code&gt;) within that table&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_5f680d5929c84f7e91d9becd2401523d-1" name="rest_code_5f680d5929c84f7e91d9becd2401523d-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;tblAttr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ContentPlaceHolderBody_gridViewLocalities"&lt;/span&gt;
&lt;a id="rest_code_5f680d5929c84f7e91d9becd2401523d-2" name="rest_code_5f680d5929c84f7e91d9becd2401523d-2"&gt;&lt;/a&gt;&lt;span class="n"&gt;restbl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tblAttr&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;a id="rest_code_5f680d5929c84f7e91d9becd2401523d-3" name="rest_code_5f680d5929c84f7e91d9becd2401523d-3"&gt;&lt;/a&gt;&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;restbl&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"tr"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Using a &lt;code class="docutils literal"&gt;for&lt;/code&gt; loop we can construct a &lt;code class="docutils literal"&gt;dict&lt;/code&gt; of the data that we actually
need. Simple!&lt;/p&gt;
&lt;p&gt;What do we do, though, when we want to get the &lt;em&gt;second&lt;/em&gt; or later pages of
result? This is where the squirrelly features and callbacks come in. The page
makes use of an &lt;code class="docutils literal"&gt;__EVENTARGUMENT&lt;/code&gt; element which is &lt;code class="docutils literal"&gt;POST&lt;/code&gt; ed as payload
back to the same url. The way that we determine this is to look for a row with
the class &lt;code class="docutils literal"&gt;pagingLink&lt;/code&gt;, then for each table data (&lt;code class="docutils literal"&gt;&amp;lt;td&amp;gt;&lt;/code&gt;) element check
for its contents matching this regex&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_97cee8dbcdeb4fb9988a02b3f7371790-1" name="rest_code_97cee8dbcdeb4fb9988a02b3f7371790-1"&gt;&lt;/a&gt;&lt;span class="s2"&gt;".*__doPostBack.'(.*?gridViewLocalities)','(Page.[0-9]+)'.*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;img alt="/images/2019/paginglinktable.png" src="https://www.jmcpdotcom.com/blog/images/2019/paginglinktable.png"&gt;
&lt;p&gt;And after that we can recursively call our query with the extra payload data
in the argument list:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_8781b43a6fa84146b15eb09a01025847-1" name="rest_code_8781b43a6fa84146b15eb09a01025847-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;queryAEC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;postcode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extrapage&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_8781b43a6fa84146b15eb09a01025847-2" name="rest_code_8781b43a6fa84146b15eb09a01025847-2"&gt;&lt;/a&gt;    &lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;a id="rest_code_8781b43a6fa84146b15eb09a01025847-3" name="rest_code_8781b43a6fa84146b15eb09a01025847-3"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    Queries the AEC url and returns soup. If extrapage is empty&lt;/span&gt;
&lt;a id="rest_code_8781b43a6fa84146b15eb09a01025847-4" name="rest_code_8781b43a6fa84146b15eb09a01025847-4"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    then we pass the soup to findFollowups before returning.&lt;/span&gt;
&lt;a id="rest_code_8781b43a6fa84146b15eb09a01025847-5" name="rest_code_8781b43a6fa84146b15eb09a01025847-5"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    """&lt;/span&gt;
&lt;a id="rest_code_8781b43a6fa84146b15eb09a01025847-6" name="rest_code_8781b43a6fa84146b15eb09a01025847-6"&gt;&lt;/a&gt;    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://electorate.aec.gov.au/LocalitySearchResults.aspx?"&lt;/span&gt;
&lt;a id="rest_code_8781b43a6fa84146b15eb09a01025847-7" name="rest_code_8781b43a6fa84146b15eb09a01025847-7"&gt;&lt;/a&gt;    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"filter=&lt;/span&gt;&lt;span class="si"&gt;{0}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;filterby=Postcode"&lt;/span&gt;
&lt;a id="rest_code_8781b43a6fa84146b15eb09a01025847-8" name="rest_code_8781b43a6fa84146b15eb09a01025847-8"&gt;&lt;/a&gt;
&lt;a id="rest_code_8781b43a6fa84146b15eb09a01025847-9" name="rest_code_8781b43a6fa84146b15eb09a01025847-9"&gt;&lt;/a&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;extrapage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_8781b43a6fa84146b15eb09a01025847-10" name="rest_code_8781b43a6fa84146b15eb09a01025847-10"&gt;&lt;/a&gt;        &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;postcode&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_8781b43a6fa84146b15eb09a01025847-11" name="rest_code_8781b43a6fa84146b15eb09a01025847-11"&gt;&lt;/a&gt;    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_8781b43a6fa84146b15eb09a01025847-12" name="rest_code_8781b43a6fa84146b15eb09a01025847-12"&gt;&lt;/a&gt;        &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"__EVENTARGUMENT"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extrapage&lt;/span&gt;
&lt;a id="rest_code_8781b43a6fa84146b15eb09a01025847-13" name="rest_code_8781b43a6fa84146b15eb09a01025847-13"&gt;&lt;/a&gt;        &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;postcode&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;I now had a &lt;a class="reference external" href="https://github.com/jmcp/grabbag/blob/master/postcode.py"&gt;script to run&lt;/a&gt;  which extracted this info and pretty-printed it
(as well as the same info in JSON):&lt;/p&gt;
&lt;pre class="code shell"&gt;&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-1" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-1"&gt;&lt;/a&gt;$ ./postcode.py &lt;span class="m"&gt;2620&lt;/span&gt;
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-2" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-2"&gt;&lt;/a&gt;State    Postcode   Locality                         Electorate
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-3" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-3"&gt;&lt;/a&gt;ACT      &lt;span class="m"&gt;2620&lt;/span&gt;       BEARD                            Canberra
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-4" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-4"&gt;&lt;/a&gt;ACT      &lt;span class="m"&gt;2620&lt;/span&gt;       BOOTH DISTRICT                   Bean
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-5" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-5"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       BURRA                            Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-6" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-6"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       CARWOOLA                         Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-7" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-7"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       CLEAR RANGE                      Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-8" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-8"&gt;&lt;/a&gt;ACT      &lt;span class="m"&gt;2620&lt;/span&gt;       CORIN DAM                        Bean
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-9" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-9"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       CRESTWOOD                        Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-10" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-10"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       ENVIRONA                         Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-11" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-11"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       FERNLEIGH PARK                   Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-12" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-12"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       GOOGONG                          Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-13" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-13"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       GREENLEIGH                       Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-14" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-14"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       GUNDAROO                         Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-15" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-15"&gt;&lt;/a&gt;ACT      &lt;span class="m"&gt;2620&lt;/span&gt;       HUME                             Bean
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-16" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-16"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       KARABAR                          Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-17" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-17"&gt;&lt;/a&gt;ACT      &lt;span class="m"&gt;2620&lt;/span&gt;       KOWEN DISTRICT                   Canberra
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-18" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-18"&gt;&lt;/a&gt;ACT      &lt;span class="m"&gt;2620&lt;/span&gt;       KOWEN FOREST                     Canberra
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-19" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-19"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       MICHELAGO                        Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-20" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-20"&gt;&lt;/a&gt;ACT      &lt;span class="m"&gt;2620&lt;/span&gt;       OAKS ESTATE                      Canberra
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-21" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-21"&gt;&lt;/a&gt;ACT      &lt;span class="m"&gt;2620&lt;/span&gt;       PADDYS RIVER DISTRICT            Bean
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-22" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-22"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       QUEANBEYAN                       Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-23" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-23"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       YARROW                           Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-24" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-24"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       QUEANBEYAN EAST                  Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-25" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-25"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       QUEANBEYAN WEST                  Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-26" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-26"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       RADCLIFFE                        Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-27" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-27"&gt;&lt;/a&gt;ACT      &lt;span class="m"&gt;2620&lt;/span&gt;       RENDEZVOUS CREEK DISTRICT        Bean
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-28" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-28"&gt;&lt;/a&gt;ACT      &lt;span class="m"&gt;2620&lt;/span&gt;       ROYALLA                          Bean
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-29" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-29"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       ROYALLA                          Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-30" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-30"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       SUTTON                           Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-31" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-31"&gt;&lt;/a&gt;ACT      &lt;span class="m"&gt;2620&lt;/span&gt;       TENNENT DISTRICT                 Bean
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-32" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-32"&gt;&lt;/a&gt;ACT      &lt;span class="m"&gt;2620&lt;/span&gt;       THARWA                           Bean
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-33" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-33"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       THARWA                           Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-34" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-34"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       THARWA                           Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-35" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-35"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       THE ANGLE                        Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-36" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-36"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       THE RIDGEWAY                     Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-37" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-37"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       TINDERRY                         Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-38" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-38"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       TRALEE                           Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-39" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-39"&gt;&lt;/a&gt;ACT      &lt;span class="m"&gt;2620&lt;/span&gt;       TUGGERANONG DISTRICT             Bean
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-40" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-40"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       URILA                            Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-41" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-41"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       WAMBOIN                          Eden-Monaro
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-42" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-42"&gt;&lt;/a&gt;ACT      &lt;span class="m"&gt;2620&lt;/span&gt;       WILLIAMSDALE                     Bean
&lt;a id="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-43" name="rest_code_18e43e57a9b146a6a85b071a1d80a2f8-43"&gt;&lt;/a&gt;NSW      &lt;span class="m"&gt;2620&lt;/span&gt;       WILLIAMSDALE                     Eden-Monaro
&lt;/pre&gt;&lt;p&gt;That really is quite a few suburbs.&lt;/p&gt;
&lt;p&gt;So now that we've got a way to extract that information, how do we make it
available and useful for everybody? With a &lt;a class="reference external" href="https://github.com/jmcp/find-my-electorate/blob/master/__init__.py"&gt;microservice&lt;/a&gt;! I hear you cry.&lt;/p&gt;
&lt;p&gt;The very first &lt;a class="reference external" href="https://github.com/jmcp/find-my-electorate/blob/master/__init__.py"&gt;microservice&lt;/a&gt; I wrote (in 2011-12, the subject of a future
post) used &lt;a class="reference external" href="https://cherrypy.org"&gt;CherryPy&lt;/a&gt;, because we'd embedded it within &lt;a class="reference external" href="https://github.com/oracle/solaris-ips"&gt;Solaris IPS&lt;/a&gt; (image
packaging system) and didn't need any further corporate approvals. The path of
least resistance. This time, however, I was unconstrained regarding approvals,
so had to choose between &lt;a class="reference external" href="https://www.djangoproject.com"&gt;Django&lt;/a&gt; and &lt;a class="reference external" href="https://palletsprojects.com/p/flask/"&gt;flask&lt;/a&gt;. For no particular reason, I
chose &lt;a class="reference external" href="https://palletsprojects.com/p/flask/"&gt;flask&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It was pretty easy to cons up the requisite templates, and write the
&lt;code class="docutils literal"&gt;/results&lt;/code&gt; method. It was at this point that my &lt;em&gt;extend the fix&lt;/em&gt; habit
(learnt via the &lt;a class="reference external" href="https://www.kepner-tregoe.com"&gt;Kepner-Tregoe&lt;/a&gt;  &lt;a class="reference external" href="https://www.kepner-tregoe.com/training-workshops/our-training-workshops/analytic-trouble-shooting/"&gt;Analytical Troubleshooting&lt;/a&gt; training many
years ago) kicked in, and I started exploring the &lt;a class="reference external" href="https://www.ecq.qld.gov.au"&gt;Electoral Commission of
Queensland&lt;/a&gt; website for the same sort of information. To my surprise, the
relatively straight-forward interface of the &lt;a class="reference external" href="https://www.aec.gov.au"&gt;AEC&lt;/a&gt; was not available, and the
closest analogue was an interactive map.&lt;/p&gt;
&lt;p&gt;After a brief phone conversation with &lt;a class="reference external" href="https://www.ecq.qld.gov.au"&gt;ECQ&lt;/a&gt; and more digging, I discovered
that the 2017 boundaries were available from &lt;a class="reference external" href="http://qldspatial.information.qld.gov.au/catalogue/custom/detail.page?fid=%7B079E7EF8-30C5-4C1D-9ABF-3D196713694F%7D"&gt;QLD Spatial&lt;/a&gt; in shapefile,
MapInfo and Google Maps KML formats. This was very useful, because KML can be
mucked about with directly using &lt;a class="reference external" href="https://www.crummy.com/software/BeautifulSoup"&gt;Beautiful Soup&lt;/a&gt;. After not too much effort I
had the latitude+longitude pairs for the boundaries extracted and stored as
&lt;code class="docutils literal"&gt;JSON&lt;/code&gt; . My phone conversation with &lt;a class="reference external" href="https://www.ecq.qld.gov.au"&gt;ECQ&lt;/a&gt; also took me down the path
of wanting to translate a street address into &lt;a class="reference external" href="https://en.wikipedia.org/wiki/GeoJSON"&gt;GeoJSON&lt;/a&gt; - and &lt;em&gt;that&lt;/em&gt; took me
to the &lt;a class="reference external" href="https://developers.google.com/maps/documentation/maps-static/dev-guide#Locations"&gt;Google Maps API&lt;/a&gt;. I did investigate &lt;a class="reference external" href="https://wiki.openstreetmap.org/wiki/API_v0.6"&gt;OpenStreetMap&lt;/a&gt;'s api, but
testing a few specific locations (addresses where we've lived over the
years) gave me significantly different latitude+longitude results. I bit the
bullet and got a &lt;a class="reference external" href="https://developers.google.com/maps/documentation/maps-static/get-api-key"&gt;Google Maps API key&lt;/a&gt; .&lt;/p&gt;
&lt;p&gt;The next step was to research how to find out if a specific point is located
within a polygon, and to my delight the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule"&gt;Even-odd rule&lt;/a&gt; has &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule#Implementation"&gt;example code&lt;/a&gt;
in &lt;a class="reference external" href="https://www.python.org"&gt;Python&lt;/a&gt;, which needed only a small change to work with my data
arrangement.&lt;/p&gt;
&lt;p&gt;With that knowledge in hand, it was time to turn the handle on the
&lt;a class="reference external" href="https://developers.google.com/maps/documentation/maps-static/dev-guide#Locations"&gt;Google Maps API&lt;/a&gt; :&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-1" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;keyarg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;amp;key=&lt;/span&gt;&lt;span class="si"&gt;{gmapkey}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-2" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-2"&gt;&lt;/a&gt;&lt;span class="n"&gt;queryurl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://maps.googleapis.com/maps/api/geocode/json?address="&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-3" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-3"&gt;&lt;/a&gt;&lt;span class="n"&gt;queryurl&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{addr}&lt;/span&gt;&lt;span class="s2"&gt; Australia"&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-4" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-4"&gt;&lt;/a&gt;&lt;span class="n"&gt;queryurl&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;keyarg&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-5" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-5"&gt;&lt;/a&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-6" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-6"&gt;&lt;/a&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-7" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-7"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Helper functions&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-8" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-8"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_geoJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-9" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-9"&gt;&lt;/a&gt;    &lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-10" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-10"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    Queries the Google Maps API for specified address, returns&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-11" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-11"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    a dict of the formatted address, the state/territory name, and&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-12" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-12"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    a float-ified version of the latitude and longitude.&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-13" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-13"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    """&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-14" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-14"&gt;&lt;/a&gt;    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryurl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gmapkey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;gmapkey&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-15" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-15"&gt;&lt;/a&gt;    &lt;span class="n"&gt;dictr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-16" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-16"&gt;&lt;/a&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s2"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"ZERO_RESULTS"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-17" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-17"&gt;&lt;/a&gt;        &lt;span class="n"&gt;dictr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"res"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-18" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-18"&gt;&lt;/a&gt;    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-19" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-19"&gt;&lt;/a&gt;        &lt;span class="n"&gt;rresj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s2"&gt;"results"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-20" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-20"&gt;&lt;/a&gt;        &lt;span class="n"&gt;dictr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"formatted_address"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rresj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"formatted_address"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-21" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-21"&gt;&lt;/a&gt;        &lt;span class="n"&gt;dictr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"latlong"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rresj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"geometry"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-22" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-22"&gt;&lt;/a&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rresj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"address_components"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-23" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-23"&gt;&lt;/a&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"types"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"administrative_area_level_1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-24" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-24"&gt;&lt;/a&gt;                &lt;span class="n"&gt;dictr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"short_name"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-25" name="rest_code_99763cf4cc7f47659f5ebedac04fcd0c-25"&gt;&lt;/a&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dictr&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;When you provide an address, we send that to Google which does a best-effort
match on the text address then returns &lt;a class="reference external" href="https://en.wikipedia.org/wiki/GeoJSON"&gt;GeoJSON&lt;/a&gt; for that match. For example,
if you enter&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;42 Wallaby Way, Sydney&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;the best-effort match will give you&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;42 Rock Wallaby Way, Blaxland NSW 2774, Australia&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I now had a way to translate a street address into a federal electorate, but
with incomplete per-State data my app wasn't finished. I managed to get
Federal, Queensland, New South Wales, Victoria and Tasmania data fairly easily
(see the links below) and South Australia's data came via personal email after
an enquiry through their contact page. I didn't get any response to several
contact attempts with either Western Australia or the Northern Territory, and
the best I could get for the ACT was their electorate to suburb associations.&lt;/p&gt;
&lt;p&gt;I remembered that the &lt;a class="reference external" href="https://www.abs.gov.au"&gt;Australian Bureau of Statistics&lt;/a&gt; has a &lt;strong&gt;standard&lt;/strong&gt;
called &lt;a class="reference external" href="https://www.abs.gov.au/websitedbs/D3310114.nsf/home/Australian+Statistical+Geography+Standard+(ASGS)"&gt;Statistical Geography&lt;/a&gt;, and the smallest unit of that is called a
&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Meshblock#Australia"&gt;Mesh Block&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mesh Blocks (MBs)&lt;/strong&gt; are the smallest geographical area defined by the
ABS. They are designed as geographic building blocks rather than as areas
for the release of statistics themselves. All statistical areas in the
ASGS, both ABS and Non ABS Structures, are built up from Mesh Blocks. As a
result the design of Mesh Blocks takes into account many factors including
administrative boundaries such as Cadastre, &lt;em&gt;Suburbs&lt;/em&gt; and &lt;em&gt;Localities&lt;/em&gt; and
LGAs as well as land uses and dwelling distribution.
(emphasis added)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Mesh Blocks are then aggregated into SA1s:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Statistical Areas Level 1 (SA1s)&lt;/strong&gt; are designed to maximise the spatial
detail available for Census data. Most SA1s have a population of between
200 to 800 persons with an average population of approximately 400
persons. This is to optimise the balance between spatial detail and the
ability to cross classify Census variables without the resulting counts
becoming too small for use. SA1s aim to separate out areas with different
geographic characteristics within Suburb and Locality boundaries. In rural
areas they often combine related Locality boundaries. &lt;em&gt;SA1s are
aggregations of Mesh Blocks.&lt;/em&gt;
(emphasis added)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With this knowledge, and a handy SA1-to-postcode map in CSV format&lt;/p&gt;
&lt;pre class="code shell"&gt;&lt;a id="rest_code_ef937a48f2af4eafb9815439b73173b9-1" name="rest_code_ef937a48f2af4eafb9815439b73173b9-1"&gt;&lt;/a&gt;$ head australia-whole/SED_2018_AUST.csv
&lt;a id="rest_code_ef937a48f2af4eafb9815439b73173b9-2" name="rest_code_ef937a48f2af4eafb9815439b73173b9-2"&gt;&lt;/a&gt;SA1_MAINCODE_2016,SED_CODE_2018,SED_NAME_2018,STATE_CODE_2016,STATE_NAME_2016,AREA_ALBERS_SQKM
&lt;a id="rest_code_ef937a48f2af4eafb9815439b73173b9-3" name="rest_code_ef937a48f2af4eafb9815439b73173b9-3"&gt;&lt;/a&gt;&lt;span class="m"&gt;10102100701&lt;/span&gt;,10031,Goulburn,1,New South Wales,362.8727
&lt;a id="rest_code_ef937a48f2af4eafb9815439b73173b9-4" name="rest_code_ef937a48f2af4eafb9815439b73173b9-4"&gt;&lt;/a&gt;&lt;span class="m"&gt;10102100702&lt;/span&gt;,10053,Monaro,1,New South Wales,229.7459
&lt;a id="rest_code_ef937a48f2af4eafb9815439b73173b9-5" name="rest_code_ef937a48f2af4eafb9815439b73173b9-5"&gt;&lt;/a&gt;&lt;span class="m"&gt;10102100703&lt;/span&gt;,10053,Monaro,1,New South Wales,2.3910
&lt;a id="rest_code_ef937a48f2af4eafb9815439b73173b9-6" name="rest_code_ef937a48f2af4eafb9815439b73173b9-6"&gt;&lt;/a&gt;&lt;span class="m"&gt;10102100704&lt;/span&gt;,10053,Monaro,1,New South Wales,1.2816
&lt;a id="rest_code_ef937a48f2af4eafb9815439b73173b9-7" name="rest_code_ef937a48f2af4eafb9815439b73173b9-7"&gt;&lt;/a&gt;&lt;span class="m"&gt;10102100705&lt;/span&gt;,10053,Monaro,1,New South Wales,1.1978
&lt;a id="rest_code_ef937a48f2af4eafb9815439b73173b9-8" name="rest_code_ef937a48f2af4eafb9815439b73173b9-8"&gt;&lt;/a&gt;....
&lt;/pre&gt;&lt;p&gt;I went looking into the SA1 information from the &lt;a class="reference external" href="https://www.abs.gov.au/ausstats/subscriber.nsf/log?openagent&amp;amp;1259030001_ste11aaust_midmif.zip&amp;amp;1259.0.30.001&amp;amp;Data%20Cubes&amp;amp;6E45E3029A27FFEFCA2578CC0012083E&amp;amp;0&amp;amp;July%202011&amp;amp;14.07.2011&amp;amp;Latest"&gt;ABS shapefile&lt;/a&gt; covering the
whole of the country. Transforming the shapefile into kml is done with
&lt;a class="reference external" href="https://gdal.org/programs/ogr2ogr.html"&gt;ogr2ogr&lt;/a&gt; and provides us with an XML schema definition. From the CSV header
line above we can see that we want the &lt;code class="docutils literal"&gt;SA1_MAINCODE_2016&lt;/code&gt; and (for
validation) the &lt;code class="docutils literal"&gt;STATE_NAME_2016&lt;/code&gt; fields. Having made a per-state list of
the SA1s, we go back to the kml and process each member of the document:&lt;/p&gt;
&lt;pre class="code xml"&gt;&lt;a id="rest_code_0abed389bf914a43ac1e37b2e5c9a098-1" name="rest_code_0abed389bf914a43ac1e37b2e5c9a098-1"&gt;&lt;/a&gt;  &lt;span class="nt"&gt;&amp;lt;gml:featureMember&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_0abed389bf914a43ac1e37b2e5c9a098-2" name="rest_code_0abed389bf914a43ac1e37b2e5c9a098-2"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;&amp;lt;ogr:SED_2018_AUST&lt;/span&gt; &lt;span class="na"&gt;fid=&lt;/span&gt;&lt;span class="s"&gt;"SED_2018_AUST.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_0abed389bf914a43ac1e37b2e5c9a098-3" name="rest_code_0abed389bf914a43ac1e37b2e5c9a098-3"&gt;&lt;/a&gt;      &lt;span class="nt"&gt;&amp;lt;ogr:geometryProperty&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_0abed389bf914a43ac1e37b2e5c9a098-4" name="rest_code_0abed389bf914a43ac1e37b2e5c9a098-4"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;&amp;lt;gml:Polygon&lt;/span&gt; &lt;span class="na"&gt;srsName=&lt;/span&gt;&lt;span class="s"&gt;"EPSG:4283"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_0abed389bf914a43ac1e37b2e5c9a098-5" name="rest_code_0abed389bf914a43ac1e37b2e5c9a098-5"&gt;&lt;/a&gt;          &lt;span class="nt"&gt;&amp;lt;gml:outerBoundaryIs&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_0abed389bf914a43ac1e37b2e5c9a098-6" name="rest_code_0abed389bf914a43ac1e37b2e5c9a098-6"&gt;&lt;/a&gt;            &lt;span class="nt"&gt;&amp;lt;gml:LinearRing&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_0abed389bf914a43ac1e37b2e5c9a098-7" name="rest_code_0abed389bf914a43ac1e37b2e5c9a098-7"&gt;&lt;/a&gt;              &lt;span class="nt"&gt;&amp;lt;gml:coordinates&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_0abed389bf914a43ac1e37b2e5c9a098-8" name="rest_code_0abed389bf914a43ac1e37b2e5c9a098-8"&gt;&lt;/a&gt;  ....
&lt;a id="rest_code_0abed389bf914a43ac1e37b2e5c9a098-9" name="rest_code_0abed389bf914a43ac1e37b2e5c9a098-9"&gt;&lt;/a&gt;              &lt;span class="nt"&gt;&amp;lt;/gml:coordinates&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_0abed389bf914a43ac1e37b2e5c9a098-10" name="rest_code_0abed389bf914a43ac1e37b2e5c9a098-10"&gt;&lt;/a&gt;            &lt;span class="nt"&gt;&amp;lt;/gml:LinearRing&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_0abed389bf914a43ac1e37b2e5c9a098-11" name="rest_code_0abed389bf914a43ac1e37b2e5c9a098-11"&gt;&lt;/a&gt;          &lt;span class="nt"&gt;&amp;lt;/gml:outerBoundaryIs&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_0abed389bf914a43ac1e37b2e5c9a098-12" name="rest_code_0abed389bf914a43ac1e37b2e5c9a098-12"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;&amp;lt;/gml:Polygon&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_0abed389bf914a43ac1e37b2e5c9a098-13" name="rest_code_0abed389bf914a43ac1e37b2e5c9a098-13"&gt;&lt;/a&gt;      &lt;span class="nt"&gt;&amp;lt;/gml:polygonMember&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_0abed389bf914a43ac1e37b2e5c9a098-14" name="rest_code_0abed389bf914a43ac1e37b2e5c9a098-14"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;&amp;lt;/ogr:geometryProperty&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_0abed389bf914a43ac1e37b2e5c9a098-15" name="rest_code_0abed389bf914a43ac1e37b2e5c9a098-15"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;&amp;lt;ogr:SED_CODE18&amp;gt;&lt;/span&gt;30028&lt;span class="nt"&gt;&amp;lt;/ogr:SED_CODE18&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_0abed389bf914a43ac1e37b2e5c9a098-16" name="rest_code_0abed389bf914a43ac1e37b2e5c9a098-16"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;&amp;lt;ogr:SED_NAME18&amp;gt;&lt;/span&gt;Gladstone&lt;span class="nt"&gt;&amp;lt;/ogr:SED_NAME18&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_0abed389bf914a43ac1e37b2e5c9a098-17" name="rest_code_0abed389bf914a43ac1e37b2e5c9a098-17"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;&amp;lt;ogr:AREASQKM18&amp;gt;&lt;/span&gt;2799.9552&lt;span class="nt"&gt;&amp;lt;/ogr:AREASQKM18&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_0abed389bf914a43ac1e37b2e5c9a098-18" name="rest_code_0abed389bf914a43ac1e37b2e5c9a098-18"&gt;&lt;/a&gt;  &lt;span class="nt"&gt;&amp;lt;/ogr:SED_2018_AUST&amp;gt;&lt;/span&gt;
&lt;a id="rest_code_0abed389bf914a43ac1e37b2e5c9a098-19" name="rest_code_0abed389bf914a43ac1e37b2e5c9a098-19"&gt;&lt;/a&gt;&lt;span class="nt"&gt;&amp;lt;/gml:featureMember&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;The &lt;code class="docutils literal"&gt;gml:coordinates&lt;/code&gt; are what we really need, they're space-separated
lat,long pairs.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_54cc95167a564893b18a8bc2a1da0ce5-1" name="rest_code_54cc95167a564893b18a8bc2a1da0ce5-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sakml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"gml:featureMember"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_54cc95167a564893b18a8bc2a1da0ce5-2" name="rest_code_54cc95167a564893b18a8bc2a1da0ce5-2"&gt;&lt;/a&gt;&lt;span class="n"&gt;sa1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ogr:SA1_MAIN16"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
&lt;a id="rest_code_54cc95167a564893b18a8bc2a1da0ce5-3" name="rest_code_54cc95167a564893b18a8bc2a1da0ce5-3"&gt;&lt;/a&gt;&lt;span class="n"&gt;mb_coord&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sa1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mb_to_points&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_54cc95167a564893b18a8bc2a1da0ce5-4" name="rest_code_54cc95167a564893b18a8bc2a1da0ce5-4"&gt;&lt;/a&gt;
&lt;a id="rest_code_54cc95167a564893b18a8bc2a1da0ce5-5" name="rest_code_54cc95167a564893b18a8bc2a1da0ce5-5"&gt;&lt;/a&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;mb_to_sed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_54cc95167a564893b18a8bc2a1da0ce5-6" name="rest_code_54cc95167a564893b18a8bc2a1da0ce5-6"&gt;&lt;/a&gt;    &lt;span class="n"&gt;electorate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mb_to_sed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_54cc95167a564893b18a8bc2a1da0ce5-7" name="rest_code_54cc95167a564893b18a8bc2a1da0ce5-7"&gt;&lt;/a&gt;    &lt;span class="n"&gt;sed_to_mb&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;electorate&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"coords"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mb_coord&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;After which we can write each jurisdiction's &lt;code class="docutils literal"&gt;dict&lt;/code&gt; of localities and lat/long
coordinates out as JSON using &lt;code class="docutils literal"&gt;json.dump(localitydict, outfile)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To confirm that I had the correct data, I wrote another simple quick-n-dirty
script &lt;a class="reference external" href="https://github.com/jmcp/grabbag/blob/master/jsoncheck.py"&gt;jsoncheck.py&lt;/a&gt; to diff the SA1-acquired JSON against my other
extractions. There was one difference of importance found - Queensland has a
new electorate &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Electoral_district_of_McConnel"&gt;McConnel&lt;/a&gt;, which was created after the most recent ABS SA1
allocation.&lt;/p&gt;
&lt;p&gt;So that's the data preparation done, back to the &lt;a class="reference external" href="https://palletsprojects.com/p/flask/"&gt;flask&lt;/a&gt; app! The app listens
at the root (&lt;code class="docutils literal"&gt;/&lt;/code&gt;), and is a simple text form. Hitting &lt;code class="docutils literal"&gt;enter&lt;/code&gt; after typing
in an address routes the &lt;code class="docutils literal"&gt;POST&lt;/code&gt; request to the &lt;a class="reference external" href="https://github.com/jmcp/find-my-electorate/blob/master/__init__.py#L206"&gt;results&lt;/a&gt; function where we
call out to the &lt;a class="reference external" href="https://developers.google.com/maps/documentation/maps-static/dev-guide#Locations"&gt;Google Maps API&lt;/a&gt;, load the relevant state JSONified
electorate list, and then locate the Federal division. There are 151 Federal
divisions, so it's not necessarily a bad thing to search through each on an
alphabetic basis and break when we get a match. I haven't figured out a way to
(time and space)-efficiently hash the coordinates vs divisions. After
determining the Federal division we then use the same method to check against
the identified state's electorate list.&lt;/p&gt;
&lt;p&gt;The first version of the app just returned the two electorate names, but I
didn't think that was very friendly, so I added another call to the
&lt;a class="reference external" href="https://developers.google.com/maps/documentation/maps-static/dev-guide#Locations"&gt;Google Maps API&lt;/a&gt; to retrieve a 400x400 image showing the supplied address on
the map; clicking on that map takes you to the larger Google-hosted map. I
also added links to the &lt;a class="reference external" href="https://www.wikipedia.org"&gt;Wikipedia&lt;/a&gt; entries for the Federal and state
electorates. To render the image's binary data we use &lt;code class="docutils literal"&gt;b64encode&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-1" name="rest_code_6b3791c94e7542e4adadac932331c2c4-1"&gt;&lt;/a&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;base64&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;b64encode&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-2" name="rest_code_6b3791c94e7542e4adadac932331c2c4-2"&gt;&lt;/a&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-3" name="rest_code_6b3791c94e7542e4adadac932331c2c4-3"&gt;&lt;/a&gt;&lt;span class="n"&gt;keyarg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;amp;key=&lt;/span&gt;&lt;span class="si"&gt;{gmapkey}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-4" name="rest_code_6b3791c94e7542e4adadac932331c2c4-4"&gt;&lt;/a&gt;&lt;span class="n"&gt;imgurl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://maps.googleapis.com/maps/api/staticmap?size=400x400"&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-5" name="rest_code_6b3791c94e7542e4adadac932331c2c4-5"&gt;&lt;/a&gt;&lt;span class="n"&gt;imgurl&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;amp;center=&lt;/span&gt;&lt;span class="si"&gt;{lati}&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="si"&gt;{longi}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;scale=1&amp;amp;maptype=roadmap&amp;amp;zoom=13"&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-6" name="rest_code_6b3791c94e7542e4adadac932331c2c4-6"&gt;&lt;/a&gt;&lt;span class="n"&gt;imgurl&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;amp;markers=X|&lt;/span&gt;&lt;span class="si"&gt;{lati}&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="si"&gt;{longi}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-7" name="rest_code_6b3791c94e7542e4adadac932331c2c4-7"&gt;&lt;/a&gt;&lt;span class="n"&gt;imgurl&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;keyarg&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-8" name="rest_code_6b3791c94e7542e4adadac932331c2c4-8"&gt;&lt;/a&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-9" name="rest_code_6b3791c94e7542e4adadac932331c2c4-9"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Let's provide a Google Maps static picture of the location&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-10" name="rest_code_6b3791c94e7542e4adadac932331c2c4-10"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Adapted from&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-11" name="rest_code_6b3791c94e7542e4adadac932331c2c4-11"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# https://stackoverflow.com/questions/25140826/generate-image-embed-in-flask-with-a-data-uri/25141268#25141268&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-12" name="rest_code_6b3791c94e7542e4adadac932331c2c4-12"&gt;&lt;/a&gt;&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-13" name="rest_code_6b3791c94e7542e4adadac932331c2c4-13"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latlong&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-14" name="rest_code_6b3791c94e7542e4adadac932331c2c4-14"&gt;&lt;/a&gt;    &lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-15" name="rest_code_6b3791c94e7542e4adadac932331c2c4-15"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    latlong -- a dict of the x and y coodinates of the location&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-16" name="rest_code_6b3791c94e7542e4adadac932331c2c4-16"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    Returns a base64-encoded image&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-17" name="rest_code_6b3791c94e7542e4adadac932331c2c4-17"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    """&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-18" name="rest_code_6b3791c94e7542e4adadac932331c2c4-18"&gt;&lt;/a&gt;    &lt;span class="n"&gt;turl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;imgurl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;longi&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;latlong&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"lng"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-19" name="rest_code_6b3791c94e7542e4adadac932331c2c4-19"&gt;&lt;/a&gt;                         &lt;span class="n"&gt;lati&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;latlong&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"lat"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-20" name="rest_code_6b3791c94e7542e4adadac932331c2c4-20"&gt;&lt;/a&gt;                         &lt;span class="n"&gt;gmapkey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;gmapkey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-21" name="rest_code_6b3791c94e7542e4adadac932331c2c4-21"&gt;&lt;/a&gt;    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;turl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-22" name="rest_code_6b3791c94e7542e4adadac932331c2c4-22"&gt;&lt;/a&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-23" name="rest_code_6b3791c94e7542e4adadac932331c2c4-23"&gt;&lt;/a&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-24" name="rest_code_6b3791c94e7542e4adadac932331c2c4-24"&gt;&lt;/a&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-25" name="rest_code_6b3791c94e7542e4adadac932331c2c4-25"&gt;&lt;/a&gt;&lt;span class="o"&gt;....&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-26" name="rest_code_6b3791c94e7542e4adadac932331c2c4-26"&gt;&lt;/a&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-27" name="rest_code_6b3791c94e7542e4adadac932331c2c4-27"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# and in the results function&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-28" name="rest_code_6b3791c94e7542e4adadac932331c2c4-28"&gt;&lt;/a&gt;    &lt;span class="n"&gt;img_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dictr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"latlong"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-29" name="rest_code_6b3791c94e7542e4adadac932331c2c4-29"&gt;&lt;/a&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-30" name="rest_code_6b3791c94e7542e4adadac932331c2c4-30"&gt;&lt;/a&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"results.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-31" name="rest_code_6b3791c94e7542e4adadac932331c2c4-31"&gt;&lt;/a&gt;                           &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-32" name="rest_code_6b3791c94e7542e4adadac932331c2c4-32"&gt;&lt;/a&gt;                           &lt;span class="n"&gt;img_data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img_data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_6b3791c94e7542e4adadac932331c2c4-33" name="rest_code_6b3791c94e7542e4adadac932331c2c4-33"&gt;&lt;/a&gt;                           &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Putting that all together gives us a rendered page that looks like this:&lt;/p&gt;
&lt;img alt="/images/2019/resultspage.png" src="https://www.jmcpdotcom.com/blog/images/2019/resultspage.png"&gt;
&lt;p&gt;To finalise the project, I ran it through &lt;a class="reference external" href="https://pypi.org/project/flake8/"&gt;flake8&lt;/a&gt; again (I do this every few
saves), and then &lt;code class="docutils literal"&gt;git commit&lt;/code&gt; followed by &lt;code class="docutils literal"&gt;git push&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;section id="reference-data-locations"&gt;
&lt;h2&gt;Reference data locations&lt;/h2&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;table class="colwidths-given"&gt;
&lt;colgroup&gt;
&lt;col style="width: 20%"&gt;
&lt;col style="width: 80%"&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th class="head"&gt;&lt;p&gt;Jurisdiction&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;URL&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;Federal&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;&lt;a class="reference external" href="https://aec.gov.au/Electorates/gis/gis_datadownload.htm"&gt;aec.gov.au/Electorates/gis/gis_datadownload.htm&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;Queensland&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;&lt;a class="reference external" href="http://qldspatial.information.qld.gov.au/catalogue/custom/detail.page?fid=%7B079E7EF8-30C5-4C1D-9ABF-3D196713694F%7D"&gt;qldspatial.information.qld.gov.au/catalogue/custom/detail.page?fid={079E7EF8-30C5-4C1D-9ABF-3D196713694F}&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;New South Wales&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;&lt;a class="reference external" href="https://elections.nsw.gov.au/Elections/How-voting-works/Electoral-boundaries"&gt;elections.nsw.gov.au/Elections/How-voting-works/Electoral-boundaries&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;Victoria&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;&lt;a class="reference external" href="http://ebc.vic.gov.au/ElectoralBoundaries/FinalElectoralBoundariesDownload.html"&gt;ebc.vic.gov.au/ElectoralBoundaries/FinalElectoralBoundariesDownload.html&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;Tasmania&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;&lt;a class="reference external" href="https://www.tec.tas.gov.au/House_of_Assembly_Elections/index.html"&gt;www.tec.tas.gov.au/House_of_Assembly_Elections/index.html&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Tasmania's state parliament has multi-member electorates, which have the same
boundaries as their 5 Federal divisions.&lt;/p&gt;
&lt;p&gt;South Australia data was provided via direct personal email.&lt;/p&gt;
&lt;p&gt;Australian Capital Territory, Western Australia and Northern Territory data
was extracted from the &lt;a class="reference external" href="https://www.abs.gov.au/ausstats/subscriber.nsf/log?openagent&amp;amp;1259030001_ste11aaust_midmif.zip&amp;amp;1259.0.30.001&amp;amp;Data%20Cubes&amp;amp;6E45E3029A27FFEFCA2578CC0012083E&amp;amp;0&amp;amp;July%202011&amp;amp;14.07.2011&amp;amp;Latest"&gt;ABS shapefile&lt;/a&gt; after &lt;code class="docutils literal"&gt;ogr2ogr&lt;/code&gt;-converting from
MapInfo Interchange Format.&lt;/p&gt;
&lt;!-- put references after this point --&gt;
&lt;/section&gt;</description><category>ETL</category><category>flask</category><category>GeoJSON</category><category>Google Maps API</category><category>JSON</category><category>KML</category><category>microservices</category><category>ogr2ogr</category><category>Python</category><category>software engineering</category><category>training</category><category>upskilling</category><guid>https://www.jmcpdotcom.com/blog/posts/2019-09-27-microservices-part-1/</guid><pubDate>Thu, 26 Sep 2019 16:00:00 GMT</pubDate></item><item><title> Where did the old posts go?</title><link>https://www.jmcpdotcom.com/blog/posts/2018-05-15-where-did-the-old-posts-go/</link><dc:creator>jmcp</dc:creator><description>&lt;p&gt;When I set up my blog in 2006, I chose to use &lt;a class="reference external" href="https://roller.apache.org"&gt;roller&lt;/a&gt;, which was the same
engine that the now-defunct &lt;a class="reference external" href="https://blogs.sun.com"&gt;blogs.sun.com&lt;/a&gt; used at the time. Later, having
gotten tired of the interface and being more impressed with &lt;a class="reference external" href="https://wordpress.org"&gt;Wordpress&lt;/a&gt;'
facility for image galleries, I started running my own instance of that.&lt;/p&gt;
&lt;p&gt;After a while, however, the frequent CVEs in both PHP and the &lt;a class="reference external" href="https://wordpress.org"&gt;Wordpress&lt;/a&gt;
base+plugin systems got me sufficiently motivated to change that I did.&lt;/p&gt;
&lt;p&gt;My friends Shawn and Liane suggested going for a static site generator, so
after having a brief look at &lt;a class="reference external" href="https://getpelican.com"&gt;Pelican&lt;/a&gt; and &lt;a class="reference external" href="https://getnikola.com"&gt;Nikola&lt;/a&gt;, I instead chose
&lt;a class="reference external" href="https://gohugo.io"&gt;Hugo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://gohugo.io"&gt;Hugo&lt;/a&gt;'s chief attraction was the relative ease with which I could create
image galleries, along with the ability to import &lt;a class="reference external" href="https://wordpress.org"&gt;Wordpress&lt;/a&gt; sites. Yay,
thought I, I can have a fairly seamless transition, and &lt;a class="reference external" href="https://www.jmcpdotcom.com/blog/posts/2017-01-18-turning-off-comments"&gt;away we went&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I didn't actually check the site backup that I made before turning off the
&lt;a class="reference external" href="https://wordpress.org"&gt;Wordpress&lt;/a&gt; instance, however, and when I went looking for the &lt;a class="reference external" href="https://www.jmcpdotcom.com/blog/posts/2012-03-14-how-to-build-a-package-archive-for-darktable/"&gt;pkgrepo
procedure&lt;/a&gt; that I used for building &lt;a class="reference external" href="https://www.darktable.org"&gt;darktable&lt;/a&gt; and didn't find the
&lt;strong&gt;content&lt;/strong&gt; that I wanted, I was a bit annoyed.&lt;/p&gt;
&lt;p&gt;Given that Solaris' packaged version of &lt;a class="reference external" href="https://golang.org"&gt;Go&lt;/a&gt; is somewhat behind the community
version, and that &lt;a class="reference external" href="https://gohugo.io"&gt;Hugo&lt;/a&gt; depends on a much newer version, this was also the
trigger for me to re-explore &lt;a class="reference external" href="https://getpelican.com"&gt;Pelican&lt;/a&gt; and &lt;a class="reference external" href="https://getnikola.com"&gt;Nikola&lt;/a&gt;, both of which are
written in &lt;a class="reference external" href="https://www.python.org"&gt;Python&lt;/a&gt;.  After a brief flirtation with &lt;a class="reference external" href="https://getpelican.com"&gt;Pelican&lt;/a&gt; I settled
instead on &lt;a class="reference external" href="https://getnikola.com"&gt;Nikola&lt;/a&gt; and did the initial &lt;a class="reference external" href="https://gohugo.io"&gt;Hugo&lt;/a&gt; to &lt;a class="reference external" href="https://getnikola.com"&gt;Nikola&lt;/a&gt; migration fairly
easily. &lt;a class="reference external" href="https://github.com/Kwpolska"&gt;Chris&lt;/a&gt; pointed me to the &lt;a class="reference external" href="https://plugins.getnikola.com/v7/gallery_directive/"&gt;gallery directive&lt;/a&gt; plugin and I was able
to make a start with some of my more recent gallery collections. A quick
implementation of a PR for &lt;a class="reference external" href="https://github.com/getnikola/nikola/pull/3018"&gt;captioned and ordered images&lt;/a&gt; got me the rest of
the way and then I could get back to the real problem: the missing content.&lt;/p&gt;
&lt;p&gt;Fortunately for me, the &lt;a class="reference external" href="https://web.archive.org"&gt;wayback machine&lt;/a&gt; had a copy of the old site entries,
and with a quick installation of the &lt;a class="reference external" href="https://github.com/hartator/wayback-machine-downloader"&gt;wayback machine downloader&lt;/a&gt; I was able
to grab the whole site as it was up to 2016.&lt;/p&gt;
&lt;p&gt;Phew!&lt;/p&gt;
&lt;p&gt;Except that all the post files were chock full of &lt;a class="reference external" href="https://wordpress.org"&gt;Wordpress&lt;/a&gt; and
&lt;a class="reference external" href="https://roller.apache.org"&gt;roller&lt;/a&gt;'s javascript and expanded css, which were a real mess to look at,
let alone extract the posts from.&lt;/p&gt;
&lt;p&gt;So I did what anybody else would do, and wrote some &lt;a class="reference external" href="https://www.python.org"&gt;Python&lt;/a&gt;  using
&lt;a class="reference external" href="https://www.crummy.com/software/BeautifulSoup/bs4/doc"&gt;BeautifulSoup&lt;/a&gt;  to provide a &lt;em&gt;best effort&lt;/em&gt; extraction which would translate
the html+js+css into the plain-text (and therefore &lt;em&gt;portable&lt;/em&gt;) &lt;a class="reference external" href="http://docutils.sourceforge.net/docs/user/rst"&gt;ReStructured
Text&lt;/a&gt; format. Now since this is a &lt;em&gt;best effort&lt;/em&gt; attempt, I'm not too
concerned about getting the output as perfect rst which matches my original
post, and I went through about 20 different entries to tidy up the &lt;em&gt;input&lt;/em&gt; so
that the script would produce something close to what I wanted. I knew that
I'd have to go and post-process quite a few entries as well, I just wanted to
not have to do &lt;em&gt;too much&lt;/em&gt; to get that going.&lt;/p&gt;
&lt;p&gt;I've converted about 340 posts extracted from the &lt;a class="reference external" href="https://web.archive.org"&gt;wayback machine&lt;/a&gt;
archive using this script &lt;a class="reference external" href="https://github.com/jmcp/grabbag/blob/master/wp-to-rest.py"&gt;wp-to-rest.py&lt;/a&gt; which took about 2 days to write
and finesse, and another day or so to muck around with several posts to get
them into better shape (ie, running &lt;code class="docutils literal"&gt;nikola build&lt;/code&gt; doesn't yell at
me). There are still a bunch of broken links in there, but at least now I've
got all my content back, and can very easily fix things up as I get the
inclination.&lt;/p&gt;</description><category>Hugo</category><category>Nikola</category><category>Pelican</category><category>Python</category><category>roller</category><category>throwaway code</category><category>Wordpress</category><guid>https://www.jmcpdotcom.com/blog/posts/2018-05-15-where-did-the-old-posts-go/</guid><pubDate>Tue, 15 May 2018 00:00:00 GMT</pubDate></item><item><title>Monitoring my PV Inverter</title><link>https://www.jmcpdotcom.com/blog/posts/2018-04-03-monitoring-my-inverter/</link><dc:creator>jmcp</dc:creator><description>&lt;p&gt;I'd like to share with you a way to build on the &lt;a class="reference external" href="https://docs.oracle.com/cd/E37838_01/html/E56520/index.html"&gt;Solaris Analytics&lt;/a&gt;
components &lt;code class="docutils literal"&gt;sstored&lt;/code&gt; and &lt;code class="docutils literal"&gt;WebUI&lt;/code&gt; that I use with our PV inverter.&lt;/p&gt;
&lt;p&gt;In April 2013 we had 15 solar panels installed on our roof, providing
3.9KW. This came with a &lt;a class="reference external" href="http://jfytech.com.au/pdf/2017/JFY-Catalog-EN-201612.pdf"&gt;JFY SunTwins 5000TL inverter&lt;/a&gt;, which
has the useful feature of data monitoring via an RS232 serial port. The
installers provided a cd with a 32bit Windows app which, while useful to
start with, did not allow me to push the generation data up to a service
like &lt;a class="reference external" href="https://pvoutput.org"&gt;pvoutput.org&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Not being interested in leaving a laptop running Windows on during
daylight hours, I searched for open source monitoring software, finding
Jonathan Croucher's &lt;a class="reference external" href="https://github.com/jcroucher/solarmonj"&gt;solarmonj&lt;/a&gt; via
&lt;a class="reference external" href="http://forums.whirlpool.net.au/archive/2003872"&gt;whirlpool&lt;/a&gt;. Importantly, I also found
a programmer reference manual for the inverter, although it is poorly
translated. I also discovered that there are a number of gaps between
what it describes and what packet analysis and the windows app actually do.&lt;/p&gt;
&lt;p&gt;I'm not too keen on C++, but it built fine on the raspberry pi that I
had available to use, and ran well enough. With a bit of shell scripting
around it I was able to upload to pvoutput.org and see how things were
going.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/jcroucher/solarmonj"&gt;Solarmonj&lt;/a&gt; has some attributes which I dislike: it's not
(wasn't, at that time) a daemon, logfile generation isn't enterprise-y, it
doesn't take command line arguments (so the device path is compiled in),
doesn't handle multiple inverters from the same daemon, and doesn't let you
send any arbitrary command listed in the spec document. It's single purpose -
but performs that purpose well enough.&lt;/p&gt;
&lt;p&gt;(Checking out Jonathan's github repo, I see that the version I was
using did in fact get an update about 3 years ago, but I had the utility in
"set and forget" mode, so never noticed).&lt;/p&gt;
&lt;p&gt;I've written a new monitor which builds on Croucher's work, with these
features: - written in Python, daemonises, is configurable, handles
multiple inverters, updates pvoutput.org and sstored as well as writing
to a local file.&lt;/p&gt;
&lt;p&gt;With this project I'm also providing sstored configuration files and a
WebUI sheet:&lt;/p&gt;
&lt;a class="reference external image-reference" href="https://www.jmcpdotcom.com/blog/images/2018/04/jfy-sheet.jpg"&gt;&lt;img alt="JFY Inverter sheet in the Solaris Analytics WebUI" src="https://www.jmcpdotcom.com/blog/images/2018/04/jfy-sheet.jpg"&gt;&lt;/a&gt;
&lt;p&gt;Zerothly, you can get all the code for this project from my
&lt;a class="reference external" href="https://github.com/jmcp/jfy-monitor"&gt;Github repo&lt;/a&gt;. It's still a work in progress but it's at the
point of being sufficient for my needs so I'm happy to share it.&lt;/p&gt;
&lt;p&gt;Since this post is about how to plug your app into
&lt;a class="reference external" href="https://docs.oracle.com/cd/E37838_01/html/E56520/index.html"&gt;Solaris Analytics&lt;/a&gt;, I won't delve too much into the SMF
and IPS components of the code.&lt;/p&gt;
&lt;p&gt;At the end of my &lt;a class="reference external" href="https://blogs.oracle.com/solaris/what-is-this-bui-thing-anyway"&gt;previous post&lt;/a&gt; on the &lt;a class="reference external" href="https://blogs.oracle.com/solaris"&gt;work blog&lt;/a&gt;
I mentioned that I would discuss using the C and Python bindings for the Stats
Store. This just post covers Python bindings, leaving detailed coverage of the
C interface for another day.&lt;/p&gt;
&lt;hr class="docutils"&gt;
&lt;p&gt;First of all, let's have a look at two basic architecture diagrams:&lt;/p&gt;


&lt;div id="gallery_container"&gt;&lt;/div&gt;
&lt;div class="row"&gt;
    &lt;div class="col-xs-6 col-md-3"&gt;
        &lt;a href="https://www.jmcpdotcom.com/blog/galleries/work/analytics-jfy/solaris-analytics-userspace-arch.jpg" class="thumbnail image-reference" title="Solaris analytics userspace arch"&gt;
            &lt;img src="https://www.jmcpdotcom.com/blog/galleries/work/analytics-jfy/solaris-analytics-userspace-arch.thumbnail.jpg" alt="Solaris analytics userspace arch"&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    &lt;div class="col-xs-6 col-md-3"&gt;
        &lt;a href="https://www.jmcpdotcom.com/blog/galleries/work/analytics-jfy/solaris-analytics-arch-block-1.jpg" class="thumbnail image-reference" title="Solaris analytics arch block 1"&gt;
            &lt;img src="https://www.jmcpdotcom.com/blog/galleries/work/analytics-jfy/solaris-analytics-arch-block-1.thumbnail.jpg" alt="Solaris analytics arch block 1"&gt;
        &lt;/a&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;hr class="docutils"&gt;
&lt;p&gt;The C and Python bindings enable read-write access to &lt;code class="docutils literal"&gt;sstored&lt;/code&gt;, so that you
can write your own provider. We call this a "userspace provider" because it
operates outside of the kernel of &lt;code class="docutils literal"&gt;sstored&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For both bindings, we have three methods of putting data into &lt;code class="docutils literal"&gt;sstored&lt;/code&gt;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;per-point synchronous   (using a &lt;code class="docutils literal"&gt;door_call()&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;per-point asynchronous  (using a shared memory region)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;bulk synchronous        (using a &lt;code class="docutils literal"&gt;door_call()&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The code that I've written for this utility is using the asynchronous method
which (at the bottom of the stack) depends on an &lt;code class="docutils literal"&gt;mmap&lt;/code&gt; region which is shared
between the daemon and the client process. Since we do not have a real speed
constraint for updating &lt;code class="docutils literal"&gt;sstored&lt;/code&gt;, I could have used the synchronous
method. I'll discuss the bulk synchronous method later.&lt;/p&gt;
&lt;p&gt;To start with, my daemon needs a connection to &lt;code class="docutils literal"&gt;sstored&lt;/code&gt;. Assuming that the
service is online, this is very simple:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_df5b60e307004cddb904f07f7a6b5151-1" name="rest_code_df5b60e307004cddb904f07f7a6b5151-1"&gt;&lt;/a&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;libsstore&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SSException&lt;/span&gt;
&lt;a id="rest_code_df5b60e307004cddb904f07f7a6b5151-2" name="rest_code_df5b60e307004cddb904f07f7a6b5151-2"&gt;&lt;/a&gt;
&lt;a id="rest_code_df5b60e307004cddb904f07f7a6b5151-3" name="rest_code_df5b60e307004cddb904f07f7a6b5151-3"&gt;&lt;/a&gt;&lt;span class="n"&gt;sst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SStore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;(I've written the daemon so that each attached inverter has its own connection
to &lt;code class="docutils literal"&gt;sstored&lt;/code&gt;, so &lt;code class="docutils literal"&gt;sst&lt;/code&gt; is a thread instance variable).&lt;/p&gt;
&lt;p&gt;The user that you run this code as must have these authorizations:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;solaris.sstore.update.res&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;solaris.sstore.write&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Add these to the user by uttering&lt;/p&gt;
&lt;pre class="code shell"&gt;&lt;a id="rest_code_166f804909cd43d8955448a58fe573f0-1" name="rest_code_166f804909cd43d8955448a58fe573f0-1"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# usermod -A +solaris.sstore.update.res,solaris.sstore.write $USER&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Once you've got those authorizations sorted, you can add the appropriate
resource to the class. I've chosen to name the resources with each inverter's
serial number. My device's serial number is 1522130110183:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-1" name="rest_code_862d12c6aac8473da3657f4505e800c7-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;RESOURCE_SSID_PREFIX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"//:class.app/solar/jfy//:res.inverter/"&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-2" name="rest_code_862d12c6aac8473da3657f4505e800c7-2"&gt;&lt;/a&gt;&lt;span class="n"&gt;STATS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-3" name="rest_code_862d12c6aac8473da3657f4505e800c7-3"&gt;&lt;/a&gt;    &lt;span class="s2"&gt;"temperature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-4" name="rest_code_862d12c6aac8473da3657f4505e800c7-4"&gt;&lt;/a&gt;    &lt;span class="s2"&gt;"power-generated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-5" name="rest_code_862d12c6aac8473da3657f4505e800c7-5"&gt;&lt;/a&gt;    &lt;span class="s2"&gt;"voltage-dc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-6" name="rest_code_862d12c6aac8473da3657f4505e800c7-6"&gt;&lt;/a&gt;    &lt;span class="s2"&gt;"current"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-7" name="rest_code_862d12c6aac8473da3657f4505e800c7-7"&gt;&lt;/a&gt;    &lt;span class="s2"&gt;"energy-generated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-8" name="rest_code_862d12c6aac8473da3657f4505e800c7-8"&gt;&lt;/a&gt;    &lt;span class="s2"&gt;"voltage-ac"&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-9" name="rest_code_862d12c6aac8473da3657f4505e800c7-9"&gt;&lt;/a&gt;    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-10" name="rest_code_862d12c6aac8473da3657f4505e800c7-10"&gt;&lt;/a&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-11" name="rest_code_862d12c6aac8473da3657f4505e800c7-11"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# hr is Human-Readable, after we've processed the binary response&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-12" name="rest_code_862d12c6aac8473da3657f4505e800c7-12"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# from the inverter&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-13" name="rest_code_862d12c6aac8473da3657f4505e800c7-13"&gt;&lt;/a&gt;&lt;span class="n"&gt;hr_serial&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1522130110183&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-14" name="rest_code_862d12c6aac8473da3657f4505e800c7-14"&gt;&lt;/a&gt;&lt;span class="n"&gt;resname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RESOURCE_SSID_PREFIX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;hr_serial&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-15" name="rest_code_862d12c6aac8473da3657f4505e800c7-15"&gt;&lt;/a&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-16" name="rest_code_862d12c6aac8473da3657f4505e800c7-16"&gt;&lt;/a&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-17" name="rest_code_862d12c6aac8473da3657f4505e800c7-17"&gt;&lt;/a&gt;    &lt;span class="n"&gt;sst&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-18" name="rest_code_862d12c6aac8473da3657f4505e800c7-18"&gt;&lt;/a&gt;    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print_warnings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-19" name="rest_code_862d12c6aac8473da3657f4505e800c7-19"&gt;&lt;/a&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;SSException&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-20" name="rest_code_862d12c6aac8473da3657f4505e800c7-20"&gt;&lt;/a&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Unable to add resource &lt;/span&gt;&lt;span class="si"&gt;{0}&lt;/span&gt;&lt;span class="s2"&gt; to sstored: &lt;/span&gt;&lt;span class="si"&gt;{1}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-21" name="rest_code_862d12c6aac8473da3657f4505e800c7-21"&gt;&lt;/a&gt;        &lt;span class="n"&gt;resname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SSException&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-22" name="rest_code_862d12c6aac8473da3657f4505e800c7-22"&gt;&lt;/a&gt;    &lt;span class="n"&gt;usesstore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-23" name="rest_code_862d12c6aac8473da3657f4505e800c7-23"&gt;&lt;/a&gt;    &lt;span class="k"&gt;raise&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-24" name="rest_code_862d12c6aac8473da3657f4505e800c7-24"&gt;&lt;/a&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-25" name="rest_code_862d12c6aac8473da3657f4505e800c7-25"&gt;&lt;/a&gt;&lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-26" name="rest_code_862d12c6aac8473da3657f4505e800c7-26"&gt;&lt;/a&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;sname&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;STATS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-27" name="rest_code_862d12c6aac8473da3657f4505e800c7-27"&gt;&lt;/a&gt;        &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{0}{1}&lt;/span&gt;&lt;span class="s2"&gt;//:stat.&lt;/span&gt;&lt;span class="si"&gt;{2}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-28" name="rest_code_862d12c6aac8473da3657f4505e800c7-28"&gt;&lt;/a&gt;            &lt;span class="n"&gt;RESOURCE_SSID_PREFIX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hr_serial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-29" name="rest_code_862d12c6aac8473da3657f4505e800c7-29"&gt;&lt;/a&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-30" name="rest_code_862d12c6aac8473da3657f4505e800c7-30"&gt;&lt;/a&gt;    &lt;span class="n"&gt;stats_array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sst&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data_attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-31" name="rest_code_862d12c6aac8473da3657f4505e800c7-31"&gt;&lt;/a&gt;    &lt;span class="n"&gt;print_warnings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-32" name="rest_code_862d12c6aac8473da3657f4505e800c7-32"&gt;&lt;/a&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;SSException&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-33" name="rest_code_862d12c6aac8473da3657f4505e800c7-33"&gt;&lt;/a&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Unable to attach stats to sstored&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{0}&lt;/span&gt;&lt;span class="s2"&gt; / &lt;/span&gt;&lt;span class="si"&gt;{1}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-34" name="rest_code_862d12c6aac8473da3657f4505e800c7-34"&gt;&lt;/a&gt;        &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;errno&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-35" name="rest_code_862d12c6aac8473da3657f4505e800c7-35"&gt;&lt;/a&gt;    &lt;span class="n"&gt;usesstore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-36" name="rest_code_862d12c6aac8473da3657f4505e800c7-36"&gt;&lt;/a&gt;    &lt;span class="n"&gt;sst&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a id="rest_code_862d12c6aac8473da3657f4505e800c7-37" name="rest_code_862d12c6aac8473da3657f4505e800c7-37"&gt;&lt;/a&gt;    &lt;span class="n"&gt;sst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Each time we query the inverter, we get back binary data which needs decoding
and extracting. This is the "feature" of the documentation which annoys me
most: it doesn't match the data packet returned, so I had to go and click
through the inverter's front panel while watching retrieved values so I could
determine the field names and units. Ugh. Anyway, inside the thread's &lt;code class="docutils literal"&gt;run()&lt;/code&gt;
method:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_54eb25826fb6493fa66e38e5fcb4714c-1" name="rest_code_54eb25826fb6493fa66e38e5fcb4714c-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query_normal_info&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a id="rest_code_54eb25826fb6493fa66e38e5fcb4714c-2" name="rest_code_54eb25826fb6493fa66e38e5fcb4714c-2"&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_54eb25826fb6493fa66e38e5fcb4714c-3" name="rest_code_54eb25826fb6493fa66e38e5fcb4714c-3"&gt;&lt;/a&gt;    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;a id="rest_code_54eb25826fb6493fa66e38e5fcb4714c-4" name="rest_code_54eb25826fb6493fa66e38e5fcb4714c-4"&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;usesstore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a id="rest_code_54eb25826fb6493fa66e38e5fcb4714c-5" name="rest_code_54eb25826fb6493fa66e38e5fcb4714c-5"&gt;&lt;/a&gt;    &lt;span class="n"&gt;sstore_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Now comes the magic:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a id="rest_code_59c605463d0b4cd0bb5fdfcd96a4fd1e-1" name="rest_code_59c605463d0b4cd0bb5fdfcd96a4fd1e-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sstore_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vals&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_59c605463d0b4cd0bb5fdfcd96a4fd1e-2" name="rest_code_59c605463d0b4cd0bb5fdfcd96a4fd1e-2"&gt;&lt;/a&gt;    &lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;a id="rest_code_59c605463d0b4cd0bb5fdfcd96a4fd1e-3" name="rest_code_59c605463d0b4cd0bb5fdfcd96a4fd1e-3"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    Updates the stats in sstored after stripping out the ignore[12]&lt;/span&gt;
&lt;a id="rest_code_59c605463d0b4cd0bb5fdfcd96a4fd1e-4" name="rest_code_59c605463d0b4cd0bb5fdfcd96a4fd1e-4"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    fields in JFYData. We're using the shared memory region method&lt;/span&gt;
&lt;a id="rest_code_59c605463d0b4cd0bb5fdfcd96a4fd1e-5" name="rest_code_59c605463d0b4cd0bb5fdfcd96a4fd1e-5"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    provided by data_attach(), so this is a very simple function.&lt;/span&gt;
&lt;a id="rest_code_59c605463d0b4cd0bb5fdfcd96a4fd1e-6" name="rest_code_59c605463d0b4cd0bb5fdfcd96a4fd1e-6"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    """&lt;/span&gt;
&lt;a id="rest_code_59c605463d0b4cd0bb5fdfcd96a4fd1e-7" name="rest_code_59c605463d0b4cd0bb5fdfcd96a4fd1e-7"&gt;&lt;/a&gt;
&lt;a id="rest_code_59c605463d0b4cd0bb5fdfcd96a4fd1e-8" name="rest_code_59c605463d0b4cd0bb5fdfcd96a4fd1e-8"&gt;&lt;/a&gt;    &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;a id="rest_code_59c605463d0b4cd0bb5fdfcd96a4fd1e-9" name="rest_code_59c605463d0b4cd0bb5fdfcd96a4fd1e-9"&gt;&lt;/a&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fname&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JFYData&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a id="rest_code_59c605463d0b4cd0bb5fdfcd96a4fd1e-10" name="rest_code_59c605463d0b4cd0bb5fdfcd96a4fd1e-10"&gt;&lt;/a&gt;        &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;JFYDivisors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_59c605463d0b4cd0bb5fdfcd96a4fd1e-11" name="rest_code_59c605463d0b4cd0bb5fdfcd96a4fd1e-11"&gt;&lt;/a&gt;    &lt;span class="n"&gt;sst&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Then we go back to sleep for 30 seconds, and repeat.&lt;/p&gt;
&lt;p&gt;An essential part of this project are the JSON metadata files that we
provide to the Stats Store. Without these, the daemon does not know
where the class, resources and statistics fit inside the namespace, nor
does it know what units or description to provide when we run &lt;code class="docutils literal"&gt;sstore
info&lt;/code&gt; for any of these statistics.&lt;/p&gt;
&lt;p&gt;All resources underneath a class must have the same statistics, and we
need to decide on the resource namespace prior to adding the class to
&lt;code class="docutils literal"&gt;sstored&lt;/code&gt;. Here is the file &lt;code class="docutils literal"&gt;class.app.solar.jfy.json&lt;/code&gt;, which in the
service/jfy package I deliver to &lt;code class="docutils literal"&gt;/usr/lib/sstore/metadata/json/site&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="code json"&gt;&lt;a id="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-1" name="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-1"&gt;&lt;/a&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-2" name="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-2"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"//:class"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-3" name="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-3"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;"copyright"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Copyright (c) 2018, James C. McPherson. All rights reserved."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-4" name="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-4"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"JFY Solar Inverter monitor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-5" name="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-5"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"app/solar/jfy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-6" name="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-6"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;"namespaces"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;a id="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-7" name="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-7"&gt;&lt;/a&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
&lt;a id="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-8" name="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-8"&gt;&lt;/a&gt;            &lt;span class="nt"&gt;"name-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-9" name="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-9"&gt;&lt;/a&gt;            &lt;span class="nt"&gt;"resource-name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"inverter"&lt;/span&gt;
&lt;a id="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-10" name="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-10"&gt;&lt;/a&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;a id="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-11" name="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-11"&gt;&lt;/a&gt;    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;a id="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-12" name="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-12"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;"stability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"stable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-13" name="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-13"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;"stat-names"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;a id="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-14" name="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-14"&gt;&lt;/a&gt;        &lt;span class="s2"&gt;"//:stat.temperature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-15" name="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-15"&gt;&lt;/a&gt;        &lt;span class="s2"&gt;"//:stat.power-generated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-16" name="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-16"&gt;&lt;/a&gt;        &lt;span class="s2"&gt;"//:stat.voltage-dc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-17" name="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-17"&gt;&lt;/a&gt;        &lt;span class="s2"&gt;"//:stat.current"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-18" name="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-18"&gt;&lt;/a&gt;        &lt;span class="s2"&gt;"//:stat.voltage-ac"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a id="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-19" name="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-19"&gt;&lt;/a&gt;        &lt;span class="s2"&gt;"//:stat.energy-generated"&lt;/span&gt;
&lt;a id="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-20" name="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-20"&gt;&lt;/a&gt;    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;a id="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-21" name="rest_code_81689c38b3de4cd5bc481a9c2c4673e4-21"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;This is validated by the daemon using the schemas shipped in
&lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;/usr/lib/sstore/metadata/json-schema&lt;/span&gt;&lt;/code&gt;, and comes with a companion file
&lt;a class="reference external" href="https://github.com/jmcp/jfy-monitor/blob/master/stat.app.solar.jfy.json"&gt;stat.app.solar.jfy.json&lt;/a&gt;. On your
&lt;a class="reference external" href="http://www.oracle.com/technetwork/server-storage/solaris11/114beta/solaris114beta-4257760.html"&gt;Solaris 11.4&lt;/a&gt; system you can check these using the
&lt;code class="docutils literal"&gt;soljsonvalidate&lt;/code&gt; utility. (Note that it's not currently possible to get
&lt;code class="docutils literal"&gt;sstored&lt;/code&gt; to dynamically re-read metadata definitions, so a &lt;code class="docutils literal"&gt;svcadm
restart sstore&lt;/code&gt; is required.&lt;/p&gt;
&lt;p&gt;The last component that we have is the WebUI sheet, which I have
packaged so that it is delivered to
&lt;code class="docutils literal"&gt;/usr/lib/webui/analytics/sheets/site&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is accessible to you once you have logged in to your
&lt;a class="reference external" href="https://127.0.0.1:6787"&gt;BUI instance&lt;/a&gt; and selected the &lt;strong&gt;Solaris Analytics&lt;/strong&gt;  app
from the menu.&lt;/p&gt;
&lt;p&gt;Prior to accessing that sheet, however, you need to configure the
service. On my system, the attached RS232 port is &lt;code class="docutils literal"&gt;/dev/term/0&lt;/code&gt;, and I
have a &lt;a class="reference external" href="https://pvoutput.org"&gt;PVOutput.org&lt;/a&gt; API Key and System ID:&lt;/p&gt;
&lt;pre class="code text"&gt;&lt;a id="rest_code_1b08fe5a2ed542d6ba03a5f090c9021f-1" name="rest_code_1b08fe5a2ed542d6ba03a5f090c9021f-1"&gt;&lt;/a&gt;# svccfg -s jfy
&lt;a id="rest_code_1b08fe5a2ed542d6ba03a5f090c9021f-2" name="rest_code_1b08fe5a2ed542d6ba03a5f090c9021f-2"&gt;&lt;/a&gt;svc:/application/jfy&amp;gt; listprop config
&lt;a id="rest_code_1b08fe5a2ed542d6ba03a5f090c9021f-3" name="rest_code_1b08fe5a2ed542d6ba03a5f090c9021f-3"&gt;&lt;/a&gt;config           application
&lt;a id="rest_code_1b08fe5a2ed542d6ba03a5f090c9021f-4" name="rest_code_1b08fe5a2ed542d6ba03a5f090c9021f-4"&gt;&lt;/a&gt;config/debug     boolean     false
&lt;a id="rest_code_1b08fe5a2ed542d6ba03a5f090c9021f-5" name="rest_code_1b08fe5a2ed542d6ba03a5f090c9021f-5"&gt;&lt;/a&gt;config/logpath   astring     /var/jfy/log/
&lt;a id="rest_code_1b08fe5a2ed542d6ba03a5f090c9021f-6" name="rest_code_1b08fe5a2ed542d6ba03a5f090c9021f-6"&gt;&lt;/a&gt;config/usesstore boolean     true
&lt;a id="rest_code_1b08fe5a2ed542d6ba03a5f090c9021f-7" name="rest_code_1b08fe5a2ed542d6ba03a5f090c9021f-7"&gt;&lt;/a&gt;svc:/application/jfy&amp;gt; listprop devterm0
&lt;a id="rest_code_1b08fe5a2ed542d6ba03a5f090c9021f-8" name="rest_code_1b08fe5a2ed542d6ba03a5f090c9021f-8"&gt;&lt;/a&gt;devterm0                 inverter
&lt;a id="rest_code_1b08fe5a2ed542d6ba03a5f090c9021f-9" name="rest_code_1b08fe5a2ed542d6ba03a5f090c9021f-9"&gt;&lt;/a&gt;devterm0/devname         astring     /dev/term/0
&lt;a id="rest_code_1b08fe5a2ed542d6ba03a5f090c9021f-10" name="rest_code_1b08fe5a2ed542d6ba03a5f090c9021f-10"&gt;&lt;/a&gt;devterm0/pvoutput_apikey astring     elided
&lt;a id="rest_code_1b08fe5a2ed542d6ba03a5f090c9021f-11" name="rest_code_1b08fe5a2ed542d6ba03a5f090c9021f-11"&gt;&lt;/a&gt;devterm0/pvoutput_sysid  count       elided
&lt;/pre&gt;&lt;p&gt;To create your system's configuration, simply add in the appropriate
definitions below. I suggest naming your inverter property group is a
way that you find useful; the constraint is that it be of the type &lt;code class="docutils literal"&gt;inverter&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="code text"&gt;&lt;a id="rest_code_9549299ea4554ef78c3ff0ce008e03f3-1" name="rest_code_9549299ea4554ef78c3ff0ce008e03f3-1"&gt;&lt;/a&gt;svc:/application/jfy&amp;gt; addpg devterm0 inverter
&lt;a id="rest_code_9549299ea4554ef78c3ff0ce008e03f3-2" name="rest_code_9549299ea4554ef78c3ff0ce008e03f3-2"&gt;&lt;/a&gt;svc:/application/jfy&amp;gt; setprop devterm0/devname = astring: "/dev/term/0"
&lt;a id="rest_code_9549299ea4554ef78c3ff0ce008e03f3-3" name="rest_code_9549299ea4554ef78c3ff0ce008e03f3-3"&gt;&lt;/a&gt;svc:/application/jfy&amp;gt; setprop devterm0/pvoutput_apikey = astring: "your api key goes here"
&lt;a id="rest_code_9549299ea4554ef78c3ff0ce008e03f3-4" name="rest_code_9549299ea4554ef78c3ff0ce008e03f3-4"&gt;&lt;/a&gt;svc:/application/jfy&amp;gt; setprop devterm0/pvoutput_sysid = count: yourSysIDgoesHere
&lt;a id="rest_code_9549299ea4554ef78c3ff0ce008e03f3-5" name="rest_code_9549299ea4554ef78c3ff0ce008e03f3-5"&gt;&lt;/a&gt;svc:/application/jfy&amp;gt; refresh
&lt;a id="rest_code_9549299ea4554ef78c3ff0ce008e03f3-6" name="rest_code_9549299ea4554ef78c3ff0ce008e03f3-6"&gt;&lt;/a&gt;svc:/application/jfy&amp;gt; quit
&lt;a id="rest_code_9549299ea4554ef78c3ff0ce008e03f3-7" name="rest_code_9549299ea4554ef78c3ff0ce008e03f3-7"&gt;&lt;/a&gt;# svcadm enable jfy
&lt;/pre&gt;&lt;p&gt;Since I'm running the service with debugging enabled, I can see copious
details in the output from&lt;/p&gt;
&lt;pre class="code text"&gt;&lt;a id="rest_code_826cbf207df841cab86e64f33f05bb16-1" name="rest_code_826cbf207df841cab86e64f33f05bb16-1"&gt;&lt;/a&gt;$ tail -f `svcs -L jfy`
&lt;/pre&gt;&lt;pre class="code shell"&gt;&lt;a id="rest_code_83f061fbe16c47a0bcad86e2f453fd99-1" name="rest_code_83f061fbe16c47a0bcad86e2f453fd99-1"&gt;&lt;/a&gt;&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="m"&gt;2018&lt;/span&gt; Apr  &lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="m"&gt;06&lt;/span&gt;:46:22 Executing start method &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/lib/svc/method/svc-jfy start"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;. &lt;span class="o"&gt;]&lt;/span&gt;
&lt;a id="rest_code_83f061fbe16c47a0bcad86e2f453fd99-2" name="rest_code_83f061fbe16c47a0bcad86e2f453fd99-2"&gt;&lt;/a&gt;args: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'/usr/lib/jfy/jfymonitor.py'&lt;/span&gt;, &lt;span class="s1"&gt;'-F'&lt;/span&gt;, &lt;span class="s1"&gt;'/var/jfy/cfg'&lt;/span&gt;, &lt;span class="s1"&gt;'-l'&lt;/span&gt;, &lt;span class="s1"&gt;'/var/jfy/log'&lt;/span&gt;, &lt;span class="s1"&gt;'-d'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;a id="rest_code_83f061fbe16c47a0bcad86e2f453fd99-3" name="rest_code_83f061fbe16c47a0bcad86e2f453fd99-3"&gt;&lt;/a&gt;
&lt;a id="rest_code_83f061fbe16c47a0bcad86e2f453fd99-4" name="rest_code_83f061fbe16c47a0bcad86e2f453fd99-4"&gt;&lt;/a&gt;response b&lt;span class="s1"&gt;'\xa5\xa5\x00\x000\xbf\x101522130110183   \xfa\xcb\n\r'&lt;/span&gt;
&lt;a id="rest_code_83f061fbe16c47a0bcad86e2f453fd99-5" name="rest_code_83f061fbe16c47a0bcad86e2f453fd99-5"&gt;&lt;/a&gt;response b&lt;span class="s1"&gt;'\xa5\xa5\x02\x010\xbe\x01\x06\xfd\xbe\n\r'&lt;/span&gt;
&lt;a id="rest_code_83f061fbe16c47a0bcad86e2f453fd99-6" name="rest_code_83f061fbe16c47a0bcad86e2f453fd99-6"&gt;&lt;/a&gt;Registration succeeded &lt;span class="k"&gt;for&lt;/span&gt; device with serial number &lt;span class="m"&gt;1522130110183&lt;/span&gt; on /dev/term/0
&lt;a id="rest_code_83f061fbe16c47a0bcad86e2f453fd99-7" name="rest_code_83f061fbe16c47a0bcad86e2f453fd99-7"&gt;&lt;/a&gt;Inverter map:
&lt;a id="rest_code_83f061fbe16c47a0bcad86e2f453fd99-8" name="rest_code_83f061fbe16c47a0bcad86e2f453fd99-8"&gt;&lt;/a&gt;id   &lt;span class="m"&gt;1&lt;/span&gt;: application
&lt;a id="rest_code_83f061fbe16c47a0bcad86e2f453fd99-9" name="rest_code_83f061fbe16c47a0bcad86e2f453fd99-9"&gt;&lt;/a&gt;id   &lt;span class="m"&gt;2&lt;/span&gt;: &lt;span class="m"&gt;1522130110183&lt;/span&gt;
&lt;a id="rest_code_83f061fbe16c47a0bcad86e2f453fd99-10" name="rest_code_83f061fbe16c47a0bcad86e2f453fd99-10"&gt;&lt;/a&gt;&lt;span class="o"&gt;{&lt;/span&gt;u&lt;span class="s1"&gt;'devterm0'&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;u&lt;span class="s1"&gt;'devname'&lt;/span&gt;: u&lt;span class="s1"&gt;'/dev/term/0'&lt;/span&gt;, u&lt;span class="s1"&gt;'pvoutput_sysid'&lt;/span&gt;: u&lt;span class="s1"&gt;'elided'&lt;/span&gt;, u&lt;span class="s1"&gt;'pvoutput_apikey'&lt;/span&gt;: u&lt;span class="s1"&gt;'elided'&lt;/span&gt;&lt;span class="o"&gt;}}&lt;/span&gt;
&lt;a id="rest_code_83f061fbe16c47a0bcad86e2f453fd99-11" name="rest_code_83f061fbe16c47a0bcad86e2f453fd99-11"&gt;&lt;/a&gt;&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="m"&gt;2018&lt;/span&gt; Apr  &lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="m"&gt;06&lt;/span&gt;:46:44 Method &lt;span class="s2"&gt;"start"&lt;/span&gt; exited with status &lt;span class="m"&gt;0&lt;/span&gt;. &lt;span class="o"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;And there you have it - a brief example of how to use the Python
bindings for the &lt;code class="docutils literal"&gt;Solaris Analytics&lt;/code&gt; feature.&lt;/p&gt;
&lt;p&gt;If you have questions or comments about this post, please send me a
message on Freenode, where I'm &lt;strong&gt;jmcp&lt;/strong&gt;. Alternatively, add a comment to
the &lt;a class="reference external" href="https://github.com/jmcp/jfy-monitor"&gt;github repo&lt;/a&gt;.&lt;/p&gt;</description><category>JFY</category><category>PV</category><category>Python</category><category>solar</category><category>Solaris</category><category>Solaris Analytics</category><category>sstore</category><category>WebUI</category><guid>https://www.jmcpdotcom.com/blog/posts/2018-04-03-monitoring-my-inverter/</guid><pubDate>Mon, 02 Apr 2018 18:00:00 GMT</pubDate></item></channel></rss>