<?xml version='1.0' encoding='UTF-8'?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
  <channel>
    <title>davep</title>
    <link>https://blog.davep.org</link>
    <description>Posts in category "Python" from davep</description>
    <atom:link href="https://blog.davep.org/feeds/python.rss.xml" rel="self"/>
    <docs>http://www.rssboard.org/rss-specification</docs>
    <generator>python-feedgen</generator>
    <language>en</language>
    <lastBuildDate>Wed, 20 May 2026 18:37:01 +0000</lastBuildDate>
    <item>
      <title>textual-dominfo</title>
      <link>https://blog.davep.org/2024/01/15/textual-dominfo.html</link>
      <description>&lt;p&gt;Last week I was wrestling with some Textual code, trying to get something to
lay out on the screen "just so". On the whole this isn't too tricky at all,
and for those times where it might feel tricky &lt;a href="https://textual.textualize.io/how-to/design-a-layout/" rel="noopener noreferrer" target="_blank"&gt;there's some advice
available on how to go about
it&lt;/a&gt;. But in this case
I was trying to do a couple of "on the edge" things and one thing I really
needed to know was what particular part of the display was being "caused" by
what container or widget&lt;sup id="fnref:30-1"&gt;&lt;a class="footnote-ref" href="#fn:30-1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Now, at the moment anyway, Textual doesn't have a full-blown devtools with
all the bells and whistles; not like in your average web browser. It does
have &lt;a href="https://textual.textualize.io/guide/devtools/" rel="noopener noreferrer" target="_blank"&gt;a devtools&lt;/a&gt;, but not
with all the fancy DOM-diving stuff the above would have needed.&lt;/p&gt;
&lt;p&gt;What I needed was the equivalent of &lt;code&gt;print&lt;/code&gt;-debugging but with a
point-and-ask interface. Now, I actually &lt;em&gt;do&lt;/em&gt; often do &lt;code&gt;print&lt;/code&gt;-debugging
with Textual apps only I use
&lt;a href="https://textual.textualize.io/api/app/#textual.app.App.notify" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;notify&lt;/code&gt;&lt;/a&gt;;
this time though &lt;code&gt;notify&lt;/code&gt; wasn't going to cut it.&lt;/p&gt;
&lt;p&gt;I needed something that would let me point at a widget and say &lt;em&gt;"show me
stuff about this"&lt;/em&gt;. Something that happens when the mouse hovers over a
widget. Something like... &lt;a href="https://textual.textualize.io/guide/widgets/#tooltips" rel="noopener noreferrer" target="_blank"&gt;a
tooltip&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;So that was easy:&lt;/p&gt;
&lt;div class="highlight" data-lang="python"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;on_mount&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;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&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;widget&lt;/span&gt; &lt;span class="ow"&gt;in&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;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;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;*&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)]:&lt;/span&gt;
        &lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tooltip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&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="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ancestors_with_self&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Suddenly I could hover my mouse over a bit of space on the screen and get a
"traceback" of sorts for what "caused" it.&lt;/p&gt;
&lt;p&gt;I posted this little hack to &lt;code&gt;#show-and-tell&lt;/code&gt; on the &lt;a href="https://discord.gg/Enf6Z3qhVr" rel="noopener noreferrer" target="_blank"&gt;Discord
server&lt;/a&gt; and someone mentioned it would be
handy if it also showed the CSS for the widget too. That was simple enough
because every widget has a &lt;code&gt;styles.css&lt;/code&gt; property that is the CSS for the
widget, as a string.&lt;/p&gt;
&lt;p&gt;After that I didn't think much more about it; until today.&lt;/p&gt;
&lt;p&gt;Looking back, one thing I realised is that adding the CSS information
&lt;code&gt;on_mount&lt;/code&gt; wasn't quite good enough, as it would only show me the state of
CSS when the mount happened, not at the moment I inspect the widget. I
needed the tooltip to be dynamic.&lt;/p&gt;
&lt;p&gt;Thing is... Textual tooltips can't be functions (which would be the obvious
approach to make it dynamic); so there was no way to get this on-the-fly
behaviour I wanted.&lt;/p&gt;
&lt;p&gt;Except there was! The type of
&lt;a href="https://textual.textualize.io/api/widget/#textual.widget.Widget.tooltip" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;tooltip&lt;/code&gt;&lt;/a&gt;
is &lt;code&gt;RenderableType&lt;/code&gt;. So that means I could assign it an object that is a
Rich renderable; that in turn means I could write a &lt;code&gt;__rich__&lt;/code&gt; method for a
class that wraps a widget and then reports back what it can see every time
it's called.&lt;/p&gt;
&lt;p&gt;In other words, via one step of indirection, I could get the &lt;em&gt;"call a
function each time"&lt;/em&gt; approach I was after!&lt;/p&gt;
&lt;p&gt;It works a treat too.&lt;/p&gt;
&lt;p&gt;All of which is a long-winded way of saying I now have a &lt;code&gt;print&lt;/code&gt;-debug-level
DOM inspector tool for when I'm building applications with Textual:&lt;/p&gt;
&lt;p&gt;&lt;img alt="textual-dominfo in action" height="550" loading="lazy" src="https://blog.davep.org/attachments/2024/01/15/textual-dominfo.webp#centre" width="622" /&gt;&lt;/p&gt;
&lt;p&gt;If this sounds handy to you, you can grab the code too. Install it into your
development environment with &lt;code&gt;pip&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight" data-lang="sh"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;textual-dominfo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and then attach it to your app or screen or some top-level widget you're
interested in via &lt;code&gt;on_mount&lt;/code&gt;; for example:&lt;/p&gt;
&lt;div class="highlight" data-lang="python"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;on_mount&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;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;textual_dominfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DOMInfo&lt;/span&gt;
    &lt;span class="n"&gt;DOMInfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attach_to&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;/pre&gt;&lt;/div&gt;

&lt;p&gt;and then hover away with that mouse cursor and inspect all the things!
Whatever you do though, &lt;em&gt;don't&lt;/em&gt; make it part of your runtime, and don't keep
it installed; just make it a development dependency.&lt;/p&gt;
&lt;p&gt;The source can be found &lt;a href="https://github.com/davep/textual-dominfo" rel="noopener noreferrer" target="_blank"&gt;over on
GitHub&lt;/a&gt; and the package is, as
mentioned above, &lt;a href="https://github.com/davep/textual-dominfo" rel="noopener noreferrer" target="_blank"&gt;over on PyPi&lt;/a&gt;.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:30-1"&gt;
&lt;p&gt;ObPedant: Containers are widgets, but it's often helpful to make a
distinction between widgets that exist just to control the layout of the
widgets inside them, and widgets that exist to actually do or show
stuff.&amp;#160;&lt;a class="footnote-backref" href="#fnref:30-1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description>
      <guid isPermaLink="false">https://blog.davep.org/2024/01/15/textual-dominfo.html</guid>
      <category>Python</category>
      <category>PyPI</category>
      <category>Python</category>
      <category>coding</category>
      <category>Textual</category>
      <pubDate>Mon, 15 Jan 2024 21:20:00 +0100</pubDate>
    </item>
    <item>
      <title>textual-countdown</title>
      <link>https://blog.davep.org/2024/01/11/textual-countdown.html</link>
      <description>&lt;p&gt;The idea for this one popped into my head while on the bus back from Textual
Towers this evening. So after dinner and some nonsense on TV I had to visit
my desk and do a quick hack.&lt;/p&gt;
&lt;p&gt;This is &lt;a href="https://github.com/davep/textual-countdown" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;textual-countdown&lt;/code&gt;&lt;/a&gt;, a
subtle but I think useful countdown widget for Textual applications.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Textual Countdown in action" loading="lazy" src="https://blog.davep.org/attachments/2024/01/11/textual-countdown.gif" /&gt;&lt;/p&gt;
&lt;p&gt;The idea is that you compose it somewhere into your screen, and when you
start the countdown the bar highlights and then starts to shrink down to
"nothing" in the middle of its display. When the countdown ends a message is
posted so you can then perform the task that was being waited for in an
event handler.&lt;/p&gt;
&lt;p&gt;Not really a novel thing, I've seen this kind of thing before on the web;
I'm sure we all have. I just thought it would be a fun idea for Textual
applications too.&lt;/p&gt;
&lt;p&gt;I envisage using this where, perhaps, an application needs to wait for an
API-visiting cooldown period, or perhaps as a subtle countdown for a
question in a quiz; something like that.&lt;/p&gt;
&lt;p&gt;Anyway, if this sounds like it's something useful for your Textual
applications, &lt;a href="https://pypi.org/project/textual-countdown/" rel="noopener noreferrer" target="_blank"&gt;it's now available from
PyPi&lt;/a&gt; and, of course, the
source is over &lt;a href="https://github.com/davep/textual-countdown" rel="noopener noreferrer" target="_blank"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;</description>
      <guid isPermaLink="false">https://blog.davep.org/2024/01/11/textual-countdown.html</guid>
      <category>Python</category>
      <category>PyPI</category>
      <category>Python</category>
      <category>coding</category>
      <category>Textual</category>
      <pubDate>Thu, 11 Jan 2024 22:52:00 +0100</pubDate>
    </item>
    <item>
      <title>astare v0.8.0 released</title>
      <link>https://blog.davep.org/2023/10/10/astare-0-8-0.html</link>
      <description>&lt;p&gt;&lt;a href="https://github.com/davep/textual-astview" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;textual-astare&lt;/code&gt;&lt;/a&gt; is another
Textual-based Python project that I've developed in the last year and I
don't believe I've mentioned on this blog. Simply put, it's a took for
viewing the abstract syntax tree of Python code, in the terminal.&lt;/p&gt;
&lt;p&gt;&lt;img alt="astare in action" height="1228" loading="lazy" src="https://blog.davep.org/attachments/2023/10/10/astare.webp#centre" width="1140" /&gt;&lt;/p&gt;
&lt;p&gt;I've just made a small update to it this evening after someone asked &lt;a href="https://github.com/davep/textual-astview/discussions/12" rel="noopener noreferrer" target="_blank"&gt;for a
sensible change I've been meaning to do for a
while&lt;/a&gt;. When I
first read the request I &lt;em&gt;was&lt;/em&gt; going to look at it next week, when I have
some time off work, but you know how it is when you sit at your desk and
have a "quick look".&lt;/p&gt;
&lt;p&gt;So anyway, yeah, &lt;a href="https://pypi.org/project/textual-astview/" rel="noopener noreferrer" target="_blank"&gt;v0.8.0&lt;/a&gt; is out
there and can be installed, with the main changes being:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Updated &lt;a href="https://github.com/davep/textual-fspicker" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;textual-fspicker&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Updated &lt;a href="https://github.com/Textualize/textual/releases/tag/v0.39.0" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;textual&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Made it so you can open a directory to browser from the command line.&lt;/li&gt;
&lt;li&gt;Made opening the current working directory the default.&lt;/li&gt;
&lt;li&gt;Tweaked the way dark/light mode get toggled so that it's now
  command-palette-friendly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I think the code does need a wee bit of tidying -- this was one of my
earliest apps built with Textual and my approach to writing Textual apps has
changed a fair bit this year, and Textual itself has grown and improved in
that time -- but it's still working well for now.&lt;/p&gt;</description>
      <guid isPermaLink="false">https://blog.davep.org/2023/10/10/astare-0-8-0.html</guid>
      <category>Python</category>
      <category>PyPI</category>
      <category>Python</category>
      <category>coding</category>
      <category>Textual</category>
      <pubDate>Tue, 10 Oct 2023 21:42:00 +0100</pubDate>
    </item>
    <item>
      <title>Mandelbrot Commands</title>
      <link>https://blog.davep.org/2023/09/29/mandelbrot-commands.html</link>
      <description>&lt;p&gt;I don't think I've mentioned it before on this blog, but some time back I
decided it would be fun to use &lt;a href="https://textual.textualize.io/" rel="noopener noreferrer" target="_blank"&gt;Textual&lt;/a&gt; to
write a Mandelbrot explorer (simple Mandelbrot explorers have been another
one of my favourite &lt;a href="https://blog.davep.org/2019/11/10/going-on-a-journey.html"&gt;known problem to try an unknown
thing&lt;/a&gt; problems). Doing it in the
terminal seemed like a fun little hack. I started off with creating
&lt;a href="https://github.com/davep/textual-canvas" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;textual-canvas&lt;/code&gt;&lt;/a&gt; and then built
&lt;a href="https://github.com/davep/textual-mandelbrot" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;textual-mandelbrot&lt;/code&gt;&lt;/a&gt; on top
of that.&lt;/p&gt;
&lt;p&gt;Not too long back &lt;a href="https://textual.textualize.io/blog/2023/09/15/textual-0370-adds-a-command-palette/" rel="noopener noreferrer" target="_blank"&gt;I added a "command palette" to
Textual&lt;/a&gt;
(I'd prefer to call it a minibuffer, but I get that that's not fashionable
these days), but so far I've not used it in any of my own projects; earlier
today I thought it could be fun to add it to &lt;code&gt;textual-mandelbrot&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Mandelbrot commands in action" height="875" loading="lazy" src="https://blog.davep.org/attachments/2023/09/29/mandelbrot-commands.webp#centre" width="962" /&gt;&lt;/p&gt;
&lt;p&gt;Most of the commands I've added are trivial and really better covered by
(and are covered by) keystrokes, but it was a good test and a way to show
off how to create a command provider.&lt;/p&gt;
&lt;p&gt;Having started this I can see some more useful things to add: for example it
might be interesting to add a facility where you can bookmark a specific
location, zoom level, iteration value, etc, and revisit later. The command
palette would feel like a great way to pull back those bookmarks.&lt;/p&gt;
&lt;p&gt;What I really liked though was how &lt;em&gt;easy&lt;/em&gt; this was to do. &lt;a href="https://github.com/davep/textual-mandelbrot/blob/main/textual_mandelbrot/commands.py" rel="noopener noreferrer" target="_blank"&gt;The code to make
the commands
available&lt;/a&gt;
is pretty trivial and, I believe, easy to follow. Although I do say so
myself I think I managed to design a very accessible API for this.&lt;/p&gt;
&lt;p&gt;There's more I'd like to add to that (the Textual command palette itself, I
mean), of course; this was just the start. Support for commands that accept
and prompt for arguments would be a neat and obvious enhancement (especially
if done in a way that's reminiscent of how commands could be defined in
&lt;a href="https://en.wikipedia.org/wiki/Common_Lisp_Interface_Manager" rel="noopener noreferrer" target="_blank"&gt;CLIM&lt;/a&gt; -- I
remember &lt;a href="https://github.com/davep/org-davep-cldict/" rel="noopener noreferrer" target="_blank"&gt;really liking&lt;/a&gt; how you
could create self-documenting and self-completing commands in that).&lt;/p&gt;
&lt;p&gt;All in good time...&lt;/p&gt;</description>
      <guid isPermaLink="false">https://blog.davep.org/2023/09/29/mandelbrot-commands.html</guid>
      <category>Python</category>
      <category>PyPI</category>
      <category>Python</category>
      <category>Textual</category>
      <category>coding</category>
      <category>mandelbrot</category>
      <category>textual-canvas</category>
      <pubDate>Fri, 29 Sep 2023 12:42:00 +0100</pubDate>
    </item>
    <item>
      <title>Textual Query Sandbox Update</title>
      <link>https://blog.davep.org/2023/09/10/textual-query-sandbox-update.html</link>
      <description>&lt;p&gt;Since &lt;a href="https://blog.davep.org/2023/09/01/textual-query-sandbox.html"&gt;quickly hacking together &lt;code&gt;textual-query-sandbox&lt;/code&gt; a few days
back&lt;/a&gt;, I've made a bunch of small
changes here and there. While most have been cosmetic and playing with some
ideas, some have also been internal improvements that should make the tool
work better.&lt;/p&gt;
&lt;p&gt;The most prominent change is one I pondered in the previous post, where I
thought it might be interesting to have a small collection of playgrounds
grounded together with a &lt;code&gt;TabbedContent&lt;/code&gt;. So as of now the tool still has
the original playground which had an emphasis on nested containers:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Playground 1" height="1076" loading="lazy" src="https://blog.davep.org/attachments/2023/09/10/tqs-1.webp#centre" width="1098" /&gt;&lt;/p&gt;
&lt;p&gt;There's now a playground with an emphasis on selecting widgets within
containers&lt;sup id="fnref:82-1"&gt;&lt;a class="footnote-ref" href="#fn:82-1"&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Playground 2" height="1076" loading="lazy" src="https://blog.davep.org/attachments/2023/09/10/tqs-2.webp#centre" width="1098" /&gt;&lt;/p&gt;
&lt;p&gt;There's also now a playground that has an emphasis on pulling out widgets
based on ID and classes:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Playground 3" height="1076" loading="lazy" src="https://blog.davep.org/attachments/2023/09/10/tqs-3.webp#centre" width="1098" /&gt;&lt;/p&gt;
&lt;p&gt;The other change you will notice from the original post is the DOM tree
shown in the bottom right corner. Note that that isn't there to show your
query result (that's the bottom left panel), it's there to help picture how
the DOM in the current playground hangs together, and will hopefully help in
picturing the structure for when you write a query.&lt;/p&gt;
&lt;p&gt;I sense there's still a lot of fun things I could add to this, and I'm still
keen on the idea of having the playgrounds "soft coded" in some way, so
people can make their own and load them up.&lt;/p&gt;
&lt;p&gt;Another thing I want to try and work on is making the display as useful as
possible. While I think it's actually pretty neat and clear, there's not a
&lt;em&gt;lot&lt;/em&gt; of space&lt;sup id="fnref:82-2"&gt;&lt;a class="footnote-ref" href="#fn:82-2"&gt;2&lt;/a&gt;&lt;/sup&gt; available to show the playground and the results. Finding
a good balance is an interesting problem.&lt;/p&gt;
&lt;p&gt;For a number of reasons this is turning into a really enjoyable tinker
project.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:82-1"&gt;
&lt;p&gt;This is, of course, slightly nonsensical wording. Containers &lt;em&gt;are&lt;/em&gt;
widgets in Textual. Pretty much everything you see in your terminal is a
widget, even a &lt;code&gt;Screen&lt;/code&gt; is a widget.&amp;#160;&lt;a class="footnote-backref" href="#fnref:82-1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:82-2"&gt;
&lt;p&gt;A lot of this of course hinges on how big someone's terminal is. I
tend to run a fairly high resolutions with the smallest font I find
readable so my terminal windows are often pretty "big"; other people
tend to have something much smaller in terms of cell with/height.&amp;#160;&lt;a class="footnote-backref" href="#fnref:82-2" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description>
      <guid isPermaLink="false">https://blog.davep.org/2023/09/10/textual-query-sandbox-update.html</guid>
      <category>Python</category>
      <category>PyPI</category>
      <category>Python</category>
      <category>coding</category>
      <category>Textual</category>
      <pubDate>Sun, 10 Sep 2023 09:22:00 +0100</pubDate>
    </item>
    <item>
      <title>Textual Query Sandbox</title>
      <link>https://blog.davep.org/2023/09/01/textual-query-sandbox.html</link>
      <description>&lt;p&gt;Sometimes I can have an idea for a Textual widget, library or application on
my ideas list for weeks, months even, before I get around to it -- mostly
just due to not having the clear time to make a run at getting it going --
and then other times an idea can pop into my head and it &lt;em&gt;has&lt;/em&gt; to be created
there and then. &lt;strong&gt;Has&lt;/strong&gt; to be!&lt;/p&gt;
&lt;p&gt;This happened yesterday evening.&lt;/p&gt;
&lt;p&gt;While the tool I built is something I'd thought of before (back around
November last year I think) it hadn't even made it to my &lt;em&gt;"list of stuff I
should make"&lt;/em&gt; that I keep in Apple Reminders; not sure why really. But then
yesterday evening a question cropped up on the &lt;a href="https://discord.gg/Enf6Z3qhVr" rel="noopener noreferrer" target="_blank"&gt;Textual Discord
server&lt;/a&gt; that related to the subject and I was
reminded of it.&lt;/p&gt;
&lt;p&gt;The subject being: &lt;a href="https://textual.textualize.io/guide/queries/" rel="noopener noreferrer" target="_blank"&gt;Textual DOM
queries&lt;/a&gt;. I like to think that
DOM queries in Textual are pretty easy to do, and well-explained in the
docs, but it's fair to admit that they need a bit of practice first, just
like any powerful tool. So I was reminded that I'd wanted to write a sandbox
application, that would have a practice DOM inside it, an input field to
type in a query, and a way of displaying the results.&lt;/p&gt;
&lt;p&gt;So &lt;a href="https://github.com/davep/textual-query-sandbox" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;textual-query-sandbox&lt;/code&gt;&lt;/a&gt;
was born!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Textual Query Sandbox" height="999" loading="lazy" src="https://blog.davep.org/attachments/2023/09/01/tqs.webp#centre" width="983" /&gt;&lt;/p&gt;
&lt;p&gt;In this very first version (which was &lt;em&gt;really&lt;/em&gt; quickly put together -- it
was something like 15 minutes to write the main code and then probably 45
minutes tweaking styles, adding all the admin stuff to allow &lt;a href="https://pypi.org/project/textual-query-sandbox/" rel="noopener noreferrer" target="_blank"&gt;deployment to
PyPi&lt;/a&gt; and writing the
README) there's an &lt;a href="https://textual.textualize.io/widgets/input/" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;Input&lt;/code&gt;&lt;/a&gt;,
a display of a group of nested containers with different IDs and classes,
and then a &lt;a href="https://textual.textualize.io/widgets/pretty/" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;Pretty&lt;/code&gt;&lt;/a&gt; widget
at the bottom to show the
&lt;a href="https://textual.textualize.io/guide/queries/#query-objects" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;query&lt;/code&gt;&lt;/a&gt;
result.&lt;/p&gt;
&lt;p&gt;If you think this looks like it might be useful to you, it can be installed
using either &lt;code&gt;pip&lt;/code&gt; or (ideally) &lt;code&gt;pipx&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight" data-lang="sh"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pipx&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;textual-query-sandbox
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and then you can run it with:&lt;/p&gt;
&lt;div class="highlight" data-lang="sh"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tqs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At which point load up the Textual query docs, type queries into the input
field, hit enter and see what gets highlighted and which widgets end up in
the result set at the bottom of the screen.&lt;/p&gt;
&lt;p&gt;Like I say: this was a quick hack yesterday evening, I think there's a lot
more can go into this. For one thing I think a more interesting practice DOM
would be a good idea, with a good mix of widgets; another thing could be
having a collection of different DOM playgrounds that can be switched
between (a &lt;code&gt;TabbedContent&lt;/code&gt; of different playgrounds could be fun here); this
could even be taken further such that the user can create their own
playground DOM to practice against.&lt;/p&gt;
&lt;p&gt;Eventually it would be neat if this could be turned into a library that can
be included in a Textual application, as a development-time debug tool, so
that on-the-fly test queries can be made.&lt;/p&gt;
&lt;p&gt;For now though, it's started, it's under way, and I think the current
version probably covers 90% of the use cases for something like this; making
for a really quick and easy tool to double-check how to query something.&lt;/p&gt;</description>
      <guid isPermaLink="false">https://blog.davep.org/2023/09/01/textual-query-sandbox.html</guid>
      <category>Python</category>
      <category>PyPI</category>
      <category>Python</category>
      <category>coding</category>
      <category>Textual</category>
      <pubDate>Fri, 01 Sep 2023 07:42:00 +0100</pubDate>
    </item>
    <item>
      <title>Unbored v0.6.0</title>
      <link>https://blog.davep.org/2023/08/13/unbored-0-6-0.html</link>
      <description>&lt;p&gt;&lt;a href="https://blog.davep.org/2022/12/01/new-things-on-pypi.html"&gt;Late on last year&lt;/a&gt; I wrote about a
bunch of new things that I'd added to PyPi, things mostly kicked off by an
early &lt;a href="https://blog.davep.org/2022/11/26/on-dog-food.html"&gt;dog-fooding session we had at textual
HQ&lt;/a&gt;. Since then I've been slowly doing my best
to keep the applications up to date with Textual.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Unbored" height="487" loading="lazy" src="https://blog.davep.org/attachments/2023/08/13/unbored.webp#centre" width="512" /&gt;&lt;/p&gt;
&lt;p&gt;As much as possible we try and not make breaking changes with the framework,
but at the same time it is still 0.x software and there's still new ways of
doing things being designed so there's going to be the odd break in approach
now and again.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/davep/unbored" rel="noopener noreferrer" target="_blank"&gt;Unbored&lt;/a&gt;, my kind of silly
self-populating TODO list application, has been sitting atop Textual 0.20.x
for a while now and earlier today I checked how it was getting in with
0.32.0 and... actually surprisingly okay. Not perfect, there were a couple
of things that had suffered from bitrot, but it wasn't crashing.&lt;/p&gt;
&lt;p&gt;The main thing I needed to change was the ability to focus a couple of
containers (they didn't used to receive focus by default, now they do so I
had to tell them not to again), and that was about it.&lt;/p&gt;
&lt;p&gt;While I was in there I also updated the application so that I dropped the
&lt;a href="https://www.youtube.com/watch?v=N2ZsXGQQpFw" rel="noopener noreferrer" target="_blank"&gt;nifty little slide-in error dialog I'd
made&lt;/a&gt;, and instead embraced
&lt;a href="https://textual.textualize.io/blog/2023/07/17/textual-0300-adds-desktop-style-notifications/" rel="noopener noreferrer" target="_blank"&gt;the new Textual notification
system&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While the application itself is a bit silly, and likely of no &lt;em&gt;real&lt;/em&gt; use to
anyone, I feel it's a pretty good barometer application, helping me check
what the experience is like when it comes to maintaining a Textual
application and the needs to keep on top of changes to Textual.&lt;/p&gt;
&lt;p&gt;It goes without saying, I hope, that really you should pin the Textual
dependency for your applications, and upgrade in a controlled and tested
way; for this though it's less crucial and is a good test of the state of
the ecosystem, and on the remote chance that anyone is using it, it'll be
helpful to me if it &lt;em&gt;does&lt;/em&gt; break and they yell.&lt;/p&gt;</description>
      <guid isPermaLink="false">https://blog.davep.org/2023/08/13/unbored-0-6-0.html</guid>
      <category>Python</category>
      <category>PyPI</category>
      <category>Python</category>
      <category>coding</category>
      <category>Textual</category>
      <pubDate>Sun, 13 Aug 2023 21:21:00 +0100</pubDate>
    </item>
    <item>
      <title>textual-canvas v0.2.0</title>
      <link>https://blog.davep.org/2023/07/16/textual-canvas-0-2-0.html</link>
      <description>&lt;p&gt;&lt;img alt="Demo of textual-canvas" height="573" loading="lazy" src="https://blog.davep.org/attachments/2023/07/16/textual-canvas.webp#centre" width="512" /&gt;&lt;/p&gt;
&lt;p&gt;Given that for a good chunk of this year I've been a bit lax about writing
here, there's a couple or so coding projects I've not written about (well,
not on &lt;em&gt;here&lt;/em&gt; anyway -- I have spoken lots about them &lt;a href="https://fosstodon.org/@davep" rel="noopener noreferrer" target="_blank"&gt;over on
Fosstodon&lt;/a&gt;). One such project is
&lt;a href="https://pypi.org/project/textual-canvas/" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;textual-canvas&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As the name might suggest, it's a "canvas" for Textual applications, which
provides a pretty basic interface for drawing pixels, lines and circles --
and of course any other shape you are able to build up from those basics.&lt;/p&gt;
&lt;p&gt;I've just &lt;a href="https://github.com/davep/textual-canvas/releases/tag/v0.2.0" rel="noopener noreferrer" target="_blank"&gt;released a quick
update&lt;/a&gt; after
it was &lt;a href="https://github.com/davep/textual-canvas/discussions/1" rel="noopener noreferrer" target="_blank"&gt;requested that I add a &lt;code&gt;clear&lt;/code&gt; method to the &lt;code&gt;Canvas&lt;/code&gt;
widget&lt;/a&gt;; a request
that makes perfect sense.&lt;/p&gt;</description>
      <guid isPermaLink="false">https://blog.davep.org/2023/07/16/textual-canvas-0-2-0.html</guid>
      <category>Python</category>
      <category>Python</category>
      <category>Textual</category>
      <category>coding</category>
      <category>PyPI</category>
      <category>textual-canvas</category>
      <pubDate>Sun, 16 Jul 2023 09:00:00 +0100</pubDate>
    </item>
    <item>
      <title>OIDIA</title>
      <link>https://blog.davep.org/2022/12/16/oidia.html</link>
      <description>&lt;p&gt;Another little thing that's up on PyPi now, which is the final bit of
fallout from &lt;a href="https://blog.davep.org/2022/12/01/new-things-on-pypi.html"&gt;the Textual dogfooding
sessions&lt;/a&gt;, is a little project I'm
calling OIDIA.&lt;/p&gt;
&lt;p&gt;The application is a streak tracker. I'm quite the fan of streak trackers.
I've used a few over the years, both to help keep me motivated and honest,
and also to help me track that I've avoided unhelpful things too. Now, most
of the apps I've used, and use now, tend to have reminders and counts and
stats and are all about "DO NOT BREAK THE STREAK OR ELSE" and that's mostly
fine, but...&lt;/p&gt;
&lt;p&gt;To keep things simple and to purely concentrate on how to build Textual
apps, I made this a "non-judgement" streak tracker. It's designed to be
really simple: you add a streak, you bump up/down the number of times you
did (or didn't do) the thing related to that streak, for each day, and
that's it.&lt;/p&gt;
&lt;p&gt;No totals. No stats. No reminders and bugging. No judgement.&lt;/p&gt;
&lt;p&gt;Here it is in action:&lt;/p&gt;
&lt;div style="text-align: center;"&gt;
    &lt;iframe
        width="560" height="315"
        src="https://www.youtube.com/embed/3Kz8eUzO9-8"
        title="YouTube video player"
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
        allowfullscreen&gt;
    &lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;When I started it, I wasn't quite sure how I wanted to store the data.
Throwing it in a SQLite database held some appeal, but that also felt like a
lot of faff for something so simple. Also, I wanted to make the data as easy
to get at, to use elsewhere, and to hack on, as possible. So in the end I
went with a simple JSON file.&lt;/p&gt;
&lt;p&gt;On macOS and GNU/Linux &lt;code&gt;streaks.json&lt;/code&gt; lives in &lt;code&gt;~/.local/share/oidia&lt;/code&gt;, on
Windows it'll be in... I'm not sure off the top of my head actually; it'll
be in whatever directory &lt;a href="https://pypi.org/project/xdg/" rel="noopener noreferrer" target="_blank"&gt;the handy &lt;code&gt;xdg&lt;/code&gt;
library&lt;/a&gt; has chosen. and because it's JSON
that means that something like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="OIDIA in action" height="834" loading="lazy" src="https://blog.davep.org/attachments/2022/12/16/oidia.webp#centre" width="922" /&gt;&lt;/p&gt;
&lt;p&gt;ends up looking like this:&lt;/p&gt;
&lt;div class="highlight" data-lang="json"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hack some Python&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;days&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-02&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-03&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-04&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-05&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-06&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-07&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-08&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-01&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-11-30&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-11-29&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-11-28&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Brush my teeth&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;days&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-02&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-03&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-04&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-05&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-06&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-07&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-08&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-01&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-11-30&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-11-29&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-11-28&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Walk&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;days&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-02&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-03&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-04&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-05&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-06&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-07&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-08&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-01&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-11-30&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-11-29&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-11-28&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Run 5k&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;days&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-03&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-05&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-11-30&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-11-28&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Run 10k&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;days&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-12-03&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2022-11-28&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Of course, it remains to be seen how well that actually scales; possibly not
so well over a long period of time, but this was written more as another way
to explore Textual than anything else. Even then, it would be pretty trivial
to update to something better for holding the data.&lt;/p&gt;
&lt;p&gt;If this seems like your thing (and I will be supporting it and onward
developing it) you &lt;a href="https://pypi.org/project/oidia/" rel="noopener noreferrer" target="_blank"&gt;can find it over on
PyPi&lt;/a&gt;, which means it can be installed with
&lt;code&gt;pip&lt;/code&gt; or the ever-handy &lt;code&gt;pipx&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight" data-lang="sh"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pipx&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;oidia
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
      <guid isPermaLink="false">https://blog.davep.org/2022/12/16/oidia.html</guid>
      <category>Python</category>
      <category>Python</category>
      <category>coding</category>
      <category>Textual</category>
      <category>PyPI</category>
      <pubDate>Fri, 16 Dec 2022 09:30:00 +0000</pubDate>
    </item>
    <item>
      <title>New Things On PyPi</title>
      <link>https://blog.davep.org/2022/12/01/new-things-on-pypi.html</link>
      <description>&lt;h2 id="an-update"&gt;An update&lt;a aria-label="Link to this heading" class="heading-anchor" href="#an-update"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So, it's fast approaching 2 months now &lt;a href="https://blog.davep.org/2022/10/05/on-to-something-new-redux.html"&gt;since I started the new
thing&lt;/a&gt; and it's been a busy
time. I've had to adjust to a quite a few new things, not least of which has
been a longer and more involved commute. I'm actually mostly enjoying it
too. While having to contend with buses isn't the &lt;em&gt;best&lt;/em&gt; thing to be doing
with my day, I do have a very fond spot for Edinburgh and it's nice to be in
there most days of the week.&lt;/p&gt;
&lt;p&gt;Part of the fallout from the new job has been that, in the last couple of
weeks, I've thrown some more stuff up on PyPi. This comes about as part of a
bit of &lt;a href="https://blog.davep.org/2022/11/26/on-dog-food.html"&gt;a dog-fooding campaign we're on at the
moment&lt;/a&gt;. While they have been, and will
continue to be, mentioned on the Textualize blog, I thought I'd give a brief
mention of them here on my own blog too given they are, essentially,
personal projects.&lt;/p&gt;
&lt;h2 id="gridinfo"&gt;gridinfo&lt;a aria-label="Link to this heading" class="heading-anchor" href="#gridinfo"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is one I'd like to tweak some more and improve on if possible. It is,
in essence, a Python-coded terminal tool that does more or less the same as
&lt;a href="https://github.com/davep/slstats.el" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;slstats.el&lt;/code&gt;&lt;/a&gt;. It started out as a
rather silly quick hack, designed to do something different with
&lt;a href="https://github.com/darrenburns/rich-pixels" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;rich-pixels&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here's the finished version (as of the time of writing) being put through
its paces:&lt;/p&gt;
&lt;div style="text-align: center;"&gt;
&lt;iframe width="560" height="315"
src="https://www.youtube.com/embed/dzpGgVPD2aM" title="YouTube video player"
frameborder="0" allow="accelerometer; autoplay; clipboard-write;
encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;Download &lt;a href="https://pypi.org/project/gridinfo/" rel="noopener noreferrer" target="_blank"&gt;from here&lt;/a&gt;, or install and
play with it with a quick &lt;code&gt;pipx install gridinfo&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="unbored"&gt;unbored&lt;a aria-label="Link to this heading" class="heading-anchor" href="#unbored"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The next experiment with Textual was to write a terminal-based client for
the &lt;a href="https://www.boredapi.com/" rel="noopener noreferrer" target="_blank"&gt;Bored-API&lt;/a&gt;. My initial plan for this was to
just have a button or two that the user could mash on and they'd get an
activity suggestion dropped into the middle of the terminal; but really that
seemed a bit boring. Then I realised that it'd be a bit more silly and a bit
more fun if I did it as a sort of TODO app. Bored? Run it up and use one of
the activities you'd generated before. Don't like any of them? Ignore them
and generate some more! Feeling bad that you've got such a backlog of
reasons to not be bored? Delete a bunch!&lt;/p&gt;
&lt;p&gt;And so on.&lt;/p&gt;
&lt;p&gt;Here's a short video of it in action:&lt;/p&gt;
&lt;div style="text-align: center;"&gt;
&lt;iframe width="560" height="315"
src="https://www.youtube.com/embed/Zl3dIzYfIWI" title="YouTube video player"
frameborder="0" allow="accelerometer; autoplay; clipboard-write;
encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;Download &lt;a href="https://pypi.org/project/unbored/" rel="noopener noreferrer" target="_blank"&gt;from here&lt;/a&gt;, or install and play
with it with a quick &lt;code&gt;pipx install unbored&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="textual-qrcode"&gt;textual-qrcode&lt;a aria-label="Link to this heading" class="heading-anchor" href="#textual-qrcode"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This one... this one I'm going to blame on the brain fog that followed flu
&lt;em&gt;and&lt;/em&gt; Covid jabs that happened the day before (and which are still kicking
my arse 4 days later). Monday morning, at my desk, and I'm wondering what to
next write to experiment with Textual, and I realised it would be
interesting to write something that would show off that it's easy to make a
third party widget library.&lt;/p&gt;
&lt;p&gt;And... yeah, I don't know why, but I remembered
&lt;a href="https://github.com/davep/qrencode.el" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;qrencode.el&lt;/code&gt;&lt;/a&gt; and so
&lt;code&gt;textual-qrcode&lt;/code&gt; was born!&lt;/p&gt;
&lt;p&gt;&lt;img alt="The most useless Textal widget yet" height="1322" loading="lazy" src="https://blog.davep.org/attachments/2022/12/01/textual-qrcode.webp#centre" width="1610" /&gt;&lt;/p&gt;
&lt;p&gt;I think the most amusing part about this is that I did it in full knowledge
that it would be useless; the idea being it would be a daft way of showing
off how you could build a widget library as an add-on for Textual. But...
more than one person actually ended up saying "yeah hold up there this could
actually be handy!"&lt;/p&gt;
&lt;p&gt;If you're one of those people... &lt;a href="https://pypi.org/project/textual-qrcode/" rel="noopener noreferrer" target="_blank"&gt;you'll find it
here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="fivepyfive"&gt;FivePyFive&lt;a aria-label="Link to this heading" class="heading-anchor" href="#fivepyfive"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While I was on a roll putting stuff up on PyPi, I also decided to tweak up
my Textual-based 5x5 and throw that up too. This was my first app built with
Textual, initially written before I'd even spoken to Will about the position
here. At one point I even did &lt;a href="https://github.com/davep/textual/blob/c4f60548922609133763c0e49f7b23aea5d44c2b/sandbox/davep/five_by_five.hy" rel="noopener noreferrer" target="_blank"&gt;a version in
Lisp&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It's since gone on to become &lt;a href="https://github.com/Textualize/textual/tree/d2ba22b86f48f4ce5b0f55767efdcf1a5478b180/examples" rel="noopener noreferrer" target="_blank"&gt;one of the example apps in Textual
itself&lt;/a&gt;
but I felt it deserved being made available to the world via an easy(ish)
install. So, if you fancy trying to crack the puzzle in your terminal, just
do a quick:&lt;/p&gt;
&lt;div class="highlight" data-lang="sh"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pipx&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;fivepyfive
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and click away.&lt;/p&gt;
&lt;div style="text-align: center;"&gt;
&lt;iframe width="343" height="610"
src="https://www.youtube.com/embed/Rf34Z5r7Q60" title="FivePyFive -- A
little annoying puzzle for the terminal" frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope;
picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;You can find it &lt;a href="https://pypi.org/project/fivepyfive/" rel="noopener noreferrer" target="_blank"&gt;over here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="pispy"&gt;PISpy&lt;a aria-label="Link to this heading" class="heading-anchor" href="#pispy"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Finally... for this week anyway, is a tool I've called PISpy. It's designed
as a simple terminal client for looking up package information on PyPi. As
of right now it's pretty straightforward, but I'd like to add more to it
over time. Here's an example of it in action:&lt;/p&gt;
&lt;div style="text-align: center;"&gt;
&lt;iframe width="560" height="315"
src="https://www.youtube.com/embed/yMGD6bXqIEo" title="YouTube video player"
frameborder="0" allow="accelerometer; autoplay; clipboard-write;
encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;One small wrinkle with publishing it to PyPi was the fact that, once I'd
chosen the name, I checked that it hadn't been used on PyPi (it hadn't) but
when it came to publishing the package it got rejected because the name was
too similar to another package! I don't know which, it wouldn't say, but
that was a problem. So in the end the published name ended up having to be
slightly different from the actual tool's name.&lt;/p&gt;
&lt;p&gt;See &lt;a href="https://pypi.org/project/pispy-client/" rel="noopener noreferrer" target="_blank"&gt;over here&lt;/a&gt; for the package, and
you can install it with a:&lt;/p&gt;
&lt;div class="highlight" data-lang="sh"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pipx&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;pispy-client
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and then just run &lt;code&gt;pispy&lt;/code&gt; in the terminal.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a aria-label="Link to this heading" class="heading-anchor" href="#conclusion"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It's been a fun couple of weeks coming up with stuff to help exercise
Textual, and there's more to come. Personally I've found the process really
helpful in that it's help me learn more about the framework and also figure
out my own approach to working with it. Each thing I've built so far has
been a small step in evolution on from what I did in the previous thing. I
doubt I've arrived at a plateau of understanding just yet.&lt;/p&gt;</description>
      <guid isPermaLink="false">https://blog.davep.org/2022/12/01/new-things-on-pypi.html</guid>
      <category>Python</category>
      <category>PyPI</category>
      <category>Python</category>
      <category>Second Life</category>
      <category>Textual</category>
      <category>coding</category>
      <pubDate>Thu, 01 Dec 2022 22:13:00 +0000</pubDate>
    </item>
    <item>
      <title>Python and macOS</title>
      <link>https://blog.davep.org/2022/11/05/python-and-macos.html</link>
      <description>&lt;h1 id="introduction"&gt;Introduction&lt;/h1&gt;
&lt;p&gt;On Twitter, a few weeks back, &lt;a href="https://twitter.com/itsBexli/status/1577332548933500928" rel="noopener noreferrer" target="_blank"&gt;@itsBexli asked me how I go about setting up
Python for development on
macOS&lt;/a&gt;. It's a
great question and one that seems to crop up in various places, and since &lt;a href="https://blog.davep.org/2015/06/27/my-first-couple-of-weeks-with-an-imac.html"&gt;I
first got into using
macOS&lt;/a&gt; and then
&lt;a href="https://blog.davep.org/2017/12/12/on_to_something_new.html"&gt;subsequently got back into coding lots in
Python&lt;/a&gt; it's absolutely an issue I ran
into.&lt;/p&gt;
&lt;p&gt;With my previous employer, while I was the only developer, I wasn't the only
person writing code and more than one other person had this issue so I
eventually wrote up my approach to solving this problem. That document is on
their internal GitLab, but I thought it worth me writing my personal
macOS/Python "rules" up again, just in case they're useful to anyone else.&lt;/p&gt;
&lt;p&gt;I am, of course, not the first person to tackle this, to document this, to
write down a good approach to this. Before and after I settled on my
approach I'd seen other people write about this. So... this post isn't here
to try and replace those, it's simply to write down my own approach, so if
anyone asks again I can point them here. I feel it's important to stress:
this isn't the only way or thoughts on this issue, there are lots of others.
Do go read them too and then settle on an approach that works for you.&lt;/p&gt;
&lt;p&gt;One other point to note, which may or may not make a difference (and may or
may not affect how this changes with time): for the past few years I've been
a heavy user of &lt;a href="https://pipenv.pypa.io/en/latest/" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;pipenv&lt;/code&gt;&lt;/a&gt; to manage my
virtual environments. This is very likely to change from now on, but keep in
mind that what follows was arrived at from the perspective of a &lt;code&gt;pipenv&lt;/code&gt;
user.&lt;/p&gt;
&lt;p&gt;So... with that admin aside...&lt;/p&gt;
&lt;h1 id="the-problem"&gt;The Problem&lt;/h1&gt;
&lt;p&gt;When I first got back into writing Python it was on macOS and, really early
on, I ran into all the usual issues: virtual environments breaking because
they were based on the system Python and it got updated, virtual
environments based on the Homebrew-installed Python and it got updated,
etc... Simply put, an occasional, annoying, non-show-stopping breaking of my
development environment which would distract me when I'd sat down to just
hack on some code, not do system admin!&lt;/p&gt;
&lt;h1 id="my-solution"&gt;My Solution&lt;/h1&gt;
&lt;p&gt;For me, what's worked for me without a problem over the past few years, in
short, is this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;NEVER&lt;/strong&gt; use the system version of Python. Just don't.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NEVER&lt;/strong&gt; use the Homebrew's own version of Python (I'm not even sure
   this is an issue any more; but it used to be).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NEVER&lt;/strong&gt; use a version of Python installed with Homebrew (or, more to
   the point, never install Python with Homebrew).&lt;/li&gt;
&lt;li&gt;Manage everything with &lt;a href="https://github.com/pyenv/" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;pyenv&lt;/code&gt;&lt;/a&gt;; which I do
   install from Homebrew.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id="the-detail"&gt;The Detail&lt;/h1&gt;
&lt;p&gt;As mentioned earlier, what I'm writing here assumes that virtual
environments are being managed with &lt;code&gt;pipenv&lt;/code&gt; (something I still do for
personal projects, for now, but this may change soon). Your choices and
mileage may vary, etc... This is what works well for me.&lt;/p&gt;
&lt;h2 id="the-one-time-items"&gt;The "one time" items&lt;a aria-label="Link to this heading" class="heading-anchor" href="#the-one-time-items"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;These are the items that need initially installing into a new macOS machine:&lt;/p&gt;
&lt;h3 id="homebrew"&gt;Homebrew&lt;a aria-label="Link to this heading" class="heading-anchor" href="#homebrew"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Unless it comes from the Mac App Store, I try and install everything via
&lt;a href="https://brew.sh/" rel="noopener noreferrer" target="_blank"&gt;Homebrew&lt;/a&gt;. It's really handy for keeping track of what
I've got installed, for recreating a development environment in general, and
for keeping things up to date.&lt;/p&gt;
&lt;h3 id="pyenv"&gt;pyenv&lt;a aria-label="Link to this heading" class="heading-anchor" href="#pyenv"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;With Homebrew installed the next step for me is to install &lt;code&gt;pyenv&lt;/code&gt;. Doing so
is as easy as:&lt;/p&gt;
&lt;div class="highlight" data-lang="sh"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;brew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;pyenv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once installed, if it's not done it for you, you may need to make some
changes to your login profile. I'm a user of &lt;a href="https://fishshell.com" rel="noopener noreferrer" target="_blank"&gt;fish&lt;/a&gt;
so I have &lt;a href="https://github.com/davep/fish/blob/efc77fd20c4bd2f36eb628730787924b6a56fcfd/conf.d/python.fish#L21-L25" rel="noopener noreferrer" target="_blank"&gt;these lines in my
setup&lt;/a&gt;.
Simply put: it asks pyenv to set up my environment so my calls to Python go
via its setup.&lt;/p&gt;
&lt;p&gt;Plenty of help on how to set up &lt;code&gt;pyenv&lt;/code&gt; can be found &lt;a href="https://github.com/pyenv/pyenv#installation" rel="noopener noreferrer" target="_blank"&gt;in its
README&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once I've done this I tend to go on and install the Python versions I'm
likely to need. For me this tends to be the most recent "active" stable
versions (as of the time of writing, 3.7 through 3.10; although I need to
now start throwing 3.11 into the mix too).&lt;/p&gt;
&lt;p&gt;I use this command:&lt;/p&gt;
&lt;div class="highlight" data-lang="sh"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pyenv&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--list
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;to see the available versions. If I want to see what's available for a
specific version I'll pipe through grep:&lt;/p&gt;
&lt;div class="highlight" data-lang="sh"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pyenv&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--list&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fgrep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;  3.9&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is handy if I want to check what the very latest release of a specific
version of Python is.&lt;/p&gt;
&lt;h3 id="the-global-python"&gt;The "Global" Python&lt;a aria-label="Link to this heading" class="heading-anchor" href="#the-global-python"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When I'm done with the above I then tend to use &lt;code&gt;pyenv&lt;/code&gt; to set my "global"
Python. This is the version I want to get when I run &lt;code&gt;python&lt;/code&gt; and I'm not
inside a virtual environment. Doing that is as simple as:&lt;/p&gt;
&lt;div class="highlight" data-lang="sh"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pyenv&lt;span class="w"&gt; &lt;/span&gt;global&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.10.7
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Of course, you'd swap the version for whatever works for you.&lt;/p&gt;
&lt;h3 id="when-stuff-breaks"&gt;When Stuff Breaks&lt;a aria-label="Link to this heading" class="heading-anchor" href="#when-stuff-breaks"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;It seems more rare these days, but on occasion I've had it such that some
update somewhere still causes my environment to break. Now though I find
that all it takes is a quick:&lt;/p&gt;
&lt;div class="highlight" data-lang="sh"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pyenv&lt;span class="w"&gt; &lt;/span&gt;rehash
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and everything is good again.&lt;/p&gt;
&lt;h2 id="setting-up-a-repo"&gt;Setting Up A Repo&lt;a aria-label="Link to this heading" class="heading-anchor" href="#setting-up-a-repo"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With all of the stuff above being mostly a one-off (or at least something I
do once when I set up a new machine -- which isn't often), the real "work"
here is when I set up a fresh repository when I start a new project. Really
it's no work at all. Again, as I've said before, I've tended to use &lt;code&gt;pipenv&lt;/code&gt;
for my own work, and still do for personal stuff (but do want to change
that), mileage may vary here depending on tool.&lt;/p&gt;
&lt;p&gt;When I start a new project I think about which Python version I want to be
working with, I ensure I have the latest version of it installed with
&lt;code&gt;pyenv&lt;/code&gt;, and then ask &lt;code&gt;pipenv&lt;/code&gt; to create a new virtual environment with
that:&lt;/p&gt;
&lt;div class="highlight" data-lang="sh"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;--python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.10.7
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When you do this, you should see &lt;code&gt;pipenv&lt;/code&gt; pulling the version of Python from
the &lt;code&gt;pyenv&lt;/code&gt; directories:&lt;/p&gt;
&lt;div class="highlight" data-lang="sh"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;pipenv&lt;span class="w"&gt; &lt;/span&gt;--python&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.10.7
Creating&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;virtualenv&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;project...
Pipfile:&lt;span class="w"&gt; &lt;/span&gt;/Users/davep/temp/cool-project/Pipfile
Using&lt;span class="w"&gt; &lt;/span&gt;/Users/davep/.pyenv/versions/3.10.7/bin/python3&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.10.7&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;virtualenv...
⠙&lt;span class="w"&gt; &lt;/span&gt;Creating&lt;span class="w"&gt; &lt;/span&gt;virtual&lt;span class="w"&gt; &lt;/span&gt;environment...created&lt;span class="w"&gt; &lt;/span&gt;virtual&lt;span class="w"&gt; &lt;/span&gt;environment&lt;span class="w"&gt; &lt;/span&gt;CPython3.10.7.final.0-64&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;795ms
&lt;span class="w"&gt;  &lt;/span&gt;creator&lt;span class="w"&gt; &lt;/span&gt;CPython3Posix&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;dest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/Users/davep/temp/cool-project/.venv,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;clear&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;False,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;no_vcs_ignore&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;False,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;global&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;False&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;seeder&lt;span class="w"&gt; &lt;/span&gt;FromAppData&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;download&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;False,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;pip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bundle,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;setuptools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bundle,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;wheel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bundle,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;via&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;copy,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;app_data_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/Users/davep/Library/Application&lt;span class="w"&gt; &lt;/span&gt;Support/virtualenv&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;added&lt;span class="w"&gt; &lt;/span&gt;seed&lt;span class="w"&gt; &lt;/span&gt;packages:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;pip&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt;.2.2,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;setuptools&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;65&lt;/span&gt;.3.0,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;wheel&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.37.1
&lt;span class="w"&gt;  &lt;/span&gt;activators&lt;span class="w"&gt; &lt;/span&gt;BashActivator,CShellActivator,FishActivator,NushellActivator,PowerShellActivator,PythonActivator
✔&lt;span class="w"&gt; &lt;/span&gt;Successfully&lt;span class="w"&gt; &lt;/span&gt;created&lt;span class="w"&gt; &lt;/span&gt;virtual&lt;span class="w"&gt; &lt;/span&gt;environment!
Virtualenv&lt;span class="w"&gt; &lt;/span&gt;location:&lt;span class="w"&gt; &lt;/span&gt;/Users/davep/temp/cool-project/.venv
Creating&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;Pipfile&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;project...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The key thing here is seeing that &lt;code&gt;pipenv&lt;/code&gt; is pulling Python from
&lt;code&gt;~/.pyenv/versions/&lt;/code&gt;. If it isn't there's a good chance you have a Python
earlier in your &lt;code&gt;PATH&lt;/code&gt; than the &lt;code&gt;pyenv&lt;/code&gt; one -- something you generally don't
want. It will work, but it's more likely to break at some point in the
future. That's the key thing I look for; if I see that I know things are
going to be okay.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a aria-label="Link to this heading" class="heading-anchor" href="#conclusion"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Since I adopted these personal rules and approaches (and really, calling
them "rules" is kind of grand -- there's almost nothing to this) I've found
I've had near-zero issues with the stability of my Python virtual
environments (and what issues I have run into tend to be trivial and of my
own doing).&lt;/p&gt;
&lt;p&gt;As I said at the start: there are, of course, other approaches to this, but
this is mine and works well for me. Do feel free to comment with your own,
I'm always happy to learn about new ways!&lt;/p&gt;</description>
      <guid isPermaLink="false">https://blog.davep.org/2022/11/05/python-and-macos.html</guid>
      <category>Python</category>
      <category>Python</category>
      <category>macOS</category>
      <category>coding</category>
      <pubDate>Sat, 05 Nov 2022 08:49:00 +0000</pubDate>
    </item>
    <item>
      <title>The PEP 8 hill I will die on</title>
      <link>https://blog.davep.org/2020/08/23/the-pep-8-hill-i-will-die-on.html</link>
      <description>&lt;p&gt;I first learnt Python back in the mid-to-late 90s, used it in place of Perl
once I was comfortable with it, and then we sort of drifted apart when I
first met Ruby. It's only in the last couple of years that I've got back
into it, and in a huge way, thanks to &lt;a href="https://blog.davep.org/2017/12/12/on_to_something_new.html"&gt;my (not-quite-so-) new
job&lt;/a&gt;. Despite
the quirks and oddness (as I perceive them), I actually quite like Python
and it's one of those languages that just flows off my fingers. I'm sure you
know the same thing, perhaps not with Python, but there will be languages
that just flow for you, and those that take a bit more effort and
concentration. Python... feels okay to me.&lt;/p&gt;
&lt;p&gt;I also appreciate that there's been a long-standing style guide. I quite
like &lt;a href="https://python.org/dev/peps/pep-0008/" rel="noopener noreferrer" target="_blank"&gt;PEP 8&lt;/a&gt; as a read, and think
there's a lot of good ideas in there; much of the content sits with how I'd
approach things if I was tasked to come up with such a document. With this
in mind, I'm a fairly heavy user of &lt;code&gt;pylint&lt;/code&gt; and it in turn leans on PEP 8
(amongst other things) and I'm happy to accept most of its judgements. Not
all of its judgements, &lt;a href="https://blog.davep.org/2019/11/04/my-pylint-shame.html"&gt;but even when I disagree with it I try and keep
track of how far I'm drifting&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But there is absolutely one hill I will happily die on when it comes to PEP
8: the concept of &lt;a href="https://www.python.org/dev/peps/pep-0008/#whitespace-in-expressions-and-statements" rel="noopener noreferrer" target="_blank"&gt;"extraneous whitespace" in lists and
expressions&lt;/a&gt;.
Just.... no! Oh gods no!&lt;/p&gt;
&lt;p&gt;To borrow a line of code from &lt;a href="https://blog.davep.org/2019/11/10/going-on-a-journey.html"&gt;the journey problem I dabbled with a while
back&lt;/a&gt;,
PEP 8 would have me write something like this:&lt;/p&gt;
&lt;div class="highlight" data-lang="python"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, I'm sure plenty of people won't see a problem with this at all; but all
I can see is an almost-claustrophobic parameter list. What's with the
parameters being jammed up against the opening and closing parens? Why have
the dinky little comma lost between two different things? Why have it look
like a long stream of letters and punctuation? Why....&lt;/p&gt;
&lt;p&gt;No.&lt;/p&gt;
&lt;p&gt;Just no.&lt;/p&gt;
&lt;p&gt;I can't.&lt;/p&gt;
&lt;p&gt;Rightly or wrongly, I just need for the code to breathe a bit. When I type
this:&lt;/p&gt;
&lt;div class="highlight" data-lang="python"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;suddenly if feels like there's fresh air in the code, like it flows gently
out of my head, off my fingers, through the keyboard and into the buffer.&lt;/p&gt;
&lt;p&gt;In my head, and to my eyes, the code is.... relaxed.&lt;/p&gt;
&lt;p&gt;Do I have a rational reason for this? Nope. Then again I don't see one for
doing it the other way either; I can't think of one and I don't see one in
the source document. So, that's a warning I always turn off with &lt;code&gt;pylint&lt;/code&gt;
and it's a style I carry through all my Python code; and I think that's the
important point here: anyone reading and working with my code should see the
same style all the way through. It might differ from PEP 8 on this point,
but at least it's the same all the way.&lt;/p&gt;
&lt;p&gt;And, really, that's okay: &lt;a href="https://www.python.org/dev/peps/pep-0008/#a-foolish-consistency-is-the-hobgoblin-of-little-minds" rel="noopener noreferrer" target="_blank"&gt;PEP 8 is there to be
ignored&lt;/a&gt;.
;-)&lt;/p&gt;
&lt;p&gt;PS: This is a small part of another blog post I was meaning to write, and
might still do, about my (still ongoing) experience of getting
&lt;a href="https://github.com/emacs-lsp/lsp-mode" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;lsp-mode&lt;/code&gt;&lt;/a&gt; up and running in Emacs
and having it play nice with Python projects. I have that working, but it
was a bit of a learning curve and epic battle over a couple of days, and one
that had me first encounter
&lt;a href="https://pypi.org/project/pycodestyle/" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;pycodestyle&lt;/code&gt;&lt;/a&gt;. I may still tell the
tale...&lt;/p&gt;</description>
      <guid isPermaLink="false">https://blog.davep.org/2020/08/23/the-pep-8-hill-i-will-die-on.html</guid>
      <category>Python</category>
      <category>Coding</category>
      <category>Python</category>
      <pubDate>Sun, 23 Aug 2020 16:54:00 +0100</pubDate>
    </item>
    <item>
      <title>git2gantt -- Simple tool to visualise coding runs</title>
      <link>https://blog.davep.org/2019/12/08/git2gantt.html</link>
      <description>&lt;p&gt;At the start of this year, as part of a much bigger process to review the
work that had taken place over the previous 12 months, I was asked (at work)
to provide some information about how much time I'd spent on various
projects. Now, for me, there's really only one project, but there's lots of
different tools and libraries that I've written to support the main work I
do. All of these are split into different repositories in the
company-internal instance of &lt;a href="https://about.gitlab.com/" rel="noopener noreferrer" target="_blank"&gt;GitLab&lt;/a&gt;. This meant
that getting a rough idea of what I was working on and when would be easy
enough -- it's all there in the commit history.&lt;/p&gt;
&lt;p&gt;Given that this information would make up a couple of slides at most during
a far bigger presentation, I wanted something that would be snappy and easy
for non-developers to follow and understand. I spent a bit of time pondering
some options and decided that (ab)using a &lt;a href="https://en.wikipedia.org/wiki/Gantt_chart" rel="noopener noreferrer" target="_blank"&gt;gantt
chart&lt;/a&gt; layout would make sense.&lt;/p&gt;
&lt;p&gt;That choice was made all the more easier given that &lt;a href="https://docs.gitlab.com/ee/user/markdown.html#mermaid" rel="noopener noreferrer" target="_blank"&gt;GitLab
supports&lt;/a&gt; the use of
&lt;a href="https://mermaid-js.github.io/mermaid/#/" rel="noopener noreferrer" target="_blank"&gt;mermaid charts&lt;/a&gt; within its
Markdown. This meant I could quickly write some code that took the git log
of each repository, turned it into mermaid code, and then render it (by
hand, this was all about getting things done quickly) via GitLab.&lt;/p&gt;
&lt;p&gt;This sounded like it could be a fun personal project. The result was some
Python code called &lt;a href="https://github.com/davep/git2gantt" rel="noopener noreferrer" target="_blank"&gt;git2gantt&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As mentioned above, the output isn't anything too clever, it's just code
that can be used to create a plot via mermaid. For example, running
git2gannt over itself:&lt;/p&gt;
&lt;div class="highlight" data-lang="text"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gantt
  title git2gantt output
  dateFormat YYYY-MM-DD

  section git2gantt
  Development: devgit2gantt20190208, 2019-02-08, 2019-02-13
  Development: devgit2gantt20190214, 2019-02-14, 2019-02-15
  Development: devgit2gantt20190303, 2019-03-03, 2019-03-04
  Development: devgit2gantt20191203, 2019-12-03, 2019-12-04
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Usage is pretty straightforward:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Usage of the tool" height="669" loading="lazy" src="https://blog.davep.org/attachments/2019/12/08/usage.webp#centre" width="764" /&gt;&lt;/p&gt;
&lt;p&gt;As you can see, it can be run over multiple repos at once, and there's also
an option to have it consider every branch within each repository. Another
handy option is the ability to limit the output to just one author --
perhaps you just want to document what you've done on a repo, not the
contributions of other people.&lt;/p&gt;
&lt;p&gt;Also especially handy, if you don't want to bore people with too much
detail, is the "fuzz" option. This lets you tell &lt;code&gt;git2gannt&lt;/code&gt; how relaxed you
want it to be when it tries to decide how long a run of work on a repo
lasted. So, perhaps, you're working on and off on a library that supports
some other system you're documenting, but you might only be making changes
every other day or so. With the correct fuzz value you can make it clear you
were working &lt;em&gt;on&lt;/em&gt; the library for a couple of weeks, despite there only
being a commit every other day.&lt;/p&gt;
&lt;p&gt;An example of running the output over a handful of projects would look
something like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Output of the tool" height="592" loading="lazy" src="https://blog.davep.org/attachments/2019/12/08/output.webp#centre" width="909" /&gt;&lt;/p&gt;
&lt;p&gt;This is one of those tools I knocked up quickly to get a job done, and
haven't quite got round to finishing off fully. One thing I'd really like to
do is add mermaid support directly within it, so that it actually has the
option to emit plots, not just mermaid code (or, perhaps, drop the mermaid
approach and use something else entirely).&lt;/p&gt;
&lt;p&gt;Meanwhile though, if you're looking for something quick and dirty that will
help you visualise what you've been working on and when for a good period of
time... perhaps this will help.&lt;/p&gt;</description>
      <guid isPermaLink="false">https://blog.davep.org/2019/12/08/git2gantt.html</guid>
      <category>Python</category>
      <category>Coding</category>
      <category>Python</category>
      <category>documentation</category>
      <pubDate>Sun, 08 Dec 2019 13:44:00 +0000</pubDate>
    </item>
    <item>
      <title>Going on a journey</title>
      <link>https://blog.davep.org/2019/11/10/going-on-a-journey.html</link>
      <description>&lt;p&gt;It's hardly a revelation to say that learning a new programming language, or
even learning software development at all, is even more difficult if you
don't have an actual problem to solve. I know I'm not alone in having pet
projects that, when faced with a new environment, I'll code up a version of
that project as a way to get familiar with and understand a language's
idioms while implementing something I know well.&lt;/p&gt;
&lt;p&gt;Personally, my two favourites are a puzzle called 5x5
(&lt;a href="https://github.com/davep/5x5.xml" rel="noopener noreferrer" target="_blank"&gt;here&lt;/a&gt;,
&lt;a href="https://github.com/davep/Chrome-5x5" rel="noopener noreferrer" target="_blank"&gt;here&lt;/a&gt;,
&lt;a href="https://github.com/davep/5x5-for-Chrome" rel="noopener noreferrer" target="_blank"&gt;here&lt;/a&gt;,
&lt;a href="https://github.com/davep/5x5-Palm" rel="noopener noreferrer" target="_blank"&gt;here&lt;/a&gt;,
&lt;a href="https://github.com/davep/5x5.el" rel="noopener noreferrer" target="_blank"&gt;here&lt;/a&gt;,
&lt;a href="https://github.com/davep/5x5-react" rel="noopener noreferrer" target="_blank"&gt;here&lt;/a&gt; and
&lt;a href="http://davep.org/misc/5x5/" rel="noopener noreferrer" target="_blank"&gt;here&lt;/a&gt;), and writing a library or even a full
application to read &lt;a href="https://en.wikipedia.org/wiki/Norton_Guides" rel="noopener noreferrer" target="_blank"&gt;Norton Guide database
files&lt;/a&gt;
(&lt;a href="https://github.com/davep/ng2html" rel="noopener noreferrer" target="_blank"&gt;here&lt;/a&gt;,
&lt;a href="https://github.com/davep/w3ng" rel="noopener noreferrer" target="_blank"&gt;here&lt;/a&gt;,
&lt;a href="https://github.com/davep/eg-OS2" rel="noopener noreferrer" target="_blank"&gt;here&lt;/a&gt;,
&lt;a href="https://github.com/davep/weg1013" rel="noopener noreferrer" target="_blank"&gt;here&lt;/a&gt;,
&lt;a href="https://github.com/davep/weg" rel="noopener noreferrer" target="_blank"&gt;here&lt;/a&gt;, &lt;a href="https://github.com/davep/eg" rel="noopener noreferrer" target="_blank"&gt;here&lt;/a&gt;,
&lt;a href="https://github.com/davep/eg.el" rel="noopener noreferrer" target="_blank"&gt;here&lt;/a&gt; and
&lt;a href="https://github.com/davep/jsNG" rel="noopener noreferrer" target="_blank"&gt;here&lt;/a&gt;). Both are fun to work on, have
practical uses, and both have the benefit of being solved problems (for me)
that let me concentrate on the "how do I do &lt;em&gt;X&lt;/em&gt; in this
language/toolkit/environment/framework/etc?".&lt;/p&gt;
&lt;p&gt;Even with those two as my goto projects, I'm always open to new small
problems that might be fun to apply to languages I do know, or languages I
want to get to know (internally at work we have a fun "league" of sorts,
writing a particular hamming distance calculation tool in different
languages, for example).&lt;/p&gt;
&lt;p&gt;A few days ago, via &lt;a href="https://github.com/Lethrir/Journeys" rel="noopener noreferrer" target="_blank"&gt;this repo on
GitHub&lt;/a&gt;, I discovered &lt;a href="https://github.com/mikehadlow/Journeys" rel="noopener noreferrer" target="_blank"&gt;this fun little
problem&lt;/a&gt;. Right away I could see the
benefit in it. As a "go away and code up a solution" interview question it
strikes me as near-perfect. It's obviously not hard to solve, but it touches
on some basic but important aspects of software development and so will
allow the developer to show off how they approach things.&lt;/p&gt;
&lt;p&gt;There's so many different approaches to it too. Even in a single language, I
could imagine having some fun writing the smallest code to solve the
problem, the most idiomatic code to solve the problem, the most supportable
and well-documented code to solve the problem, etc. And then there's the
thing I talk about above: knowing the solution and knowing it's easy, you
can then use it to learn the idiomatic way of solving the problem in new
languages.&lt;/p&gt;
&lt;p&gt;Even better, &lt;a href="https://github.com/mikehadlow/Journeys#other-solutions" rel="noopener noreferrer" target="_blank"&gt;the README of the original repo links to solutions others have
written&lt;/a&gt;. Knowing
the problem, and knowing the solution, you can then go and read other
people's code and learn something about different styles and different
languages.&lt;/p&gt;
&lt;p&gt;Over the next few weeks, as I get free time, I think I might just do this.
Take the "Journeys" problem and write versions in different languages I work
with, or know, and also use it to get to know languages I've yet to know or
use heavily (I'm especially keen to try a version in
&lt;a href="https://julialang.org/" rel="noopener noreferrer" target="_blank"&gt;Julia&lt;/a&gt; -- a language I really like the look of and
want to find a reason to use).&lt;/p&gt;
&lt;p&gt;Meanwhile, yesterday, I had a quick go at a first version in Python (aimed
at Python 3.8 or higher):
&lt;a href="https://github.com/davep/journeys.py" rel="noopener noreferrer" target="_blank"&gt;https://github.com/davep/journeys.py&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I set out to try and write something that was fairly idiomatic Python, which
uses tools that I tend to employ when working on Python projects (pipenv,
make, etc), and which also &lt;a href="https://docs.python.org/3/library/dataclasses.html" rel="noopener noreferrer" target="_blank"&gt;used something I've never quite found a need for
so far&lt;/a&gt; in my usual
coding, but which I can see being useful and helpful.&lt;/p&gt;
&lt;p&gt;I even threw in a couple of uses of &lt;a href="https://www.python.org/dev/peps/pep-0572/" rel="noopener noreferrer" target="_blank"&gt;PEP
572&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;I can see me tinkering with this some more over the next few days. I can
even see me writing a very different implementation in Python, just for the
fun of it.&lt;/p&gt;
&lt;p&gt;I think that's what I like about this little problem. It's a good way to do
a bit of programming exercise; it's like the perfect way to do the
programming equivalent of going for a short run.&lt;/p&gt;</description>
      <guid isPermaLink="false">https://blog.davep.org/2019/11/10/going-on-a-journey.html</guid>
      <category>Python</category>
      <category>Coding</category>
      <category>Python</category>
      <pubDate>Sun, 10 Nov 2019 14:32:00 +0000</pubDate>
    </item>
    <item>
      <title>My Pylint shame</title>
      <link>https://blog.davep.org/2019/11/04/my-pylint-shame.html</link>
      <description>&lt;p&gt;I first got into Python in the mid-to-late 1990s. It's so far back that I
think the copy of &lt;a href="https://www.python.org/doc/essays/foreword/" rel="noopener noreferrer" target="_blank"&gt;Programming
Python&lt;/a&gt; that I have (sadly in
storage at the moment) might be a first edition. I probably fell out of the
habit of using Python some time in the early 2000s (that was when I met
Ruby). It was only 22 months ago that I started using Python a &lt;em&gt;lot&lt;/em&gt; thanks
to &lt;a href="https://blog.davep.org/2017/12/12/on_to_something_new.html"&gt;a change of
employer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As you might imagine, much had changed in the 15+ years since I'd last
written a line of Python in anger. So, early on, I made a point of making
&lt;a href="https://www.pylint.org/" rel="noopener noreferrer" target="_blank"&gt;Pylint&lt;/a&gt; part of my development process. All my
projects have a &lt;code&gt;make lint&lt;/code&gt; make target. All of my projects lint the code
when I push to &lt;code&gt;master&lt;/code&gt; in the company GitLab instance. These days I even
use &lt;a href="https://www.flycheck.org/en/latest/" rel="noopener noreferrer" target="_blank"&gt;flycheck&lt;/a&gt; to keep me honest as I
write my code; mostly gone are the days where I don't know of problems until
I do a &lt;code&gt;make lint&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Leaning on Pylint in the early days of my new position made for a great
Python refresher for me. Now, I still lean on it to make sure I don't make
daft mistakes.&lt;/p&gt;
&lt;p&gt;But...&lt;/p&gt;
&lt;p&gt;Pylint and I don't always agree. And that's fine. For example, I really
can't stand Pylint's approach to whitespace, and that is a hill I'll happily
die on. Ditto the obsession with lines being no more than 80 characters wide
(120 should be fine thanks). As such any project's &lt;code&gt;.pylintrc&lt;/code&gt; has, as a
bare minimum, this:&lt;/p&gt;
&lt;div class="highlight" data-lang="text"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;[FORMAT]
max-line-length=120

[MESSAGES CONTROL]
disable=bad-whitespace
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Beyond that though, aside from one or two extras that pertain to particular
projects, I'm happy with what Pylint complains about.&lt;/p&gt;
&lt;p&gt;There are exceptions though. There are times, simply due to the nature of
the code involved, that Pylint's insistence on code purity isn't going to
work. That's where I use its inline &lt;a href="https://pylint.readthedocs.io/en/latest/user_guide/message-control.html#block-disables" rel="noopener noreferrer" target="_blank"&gt;block disabling
feature&lt;/a&gt;.
It's handy and helps keep things clean (I won't deploy code that doesn't
pass 10/10), but there is always this nagging doubt: if I've disabled a
warning in the code, am I ever going to come back and revisit it?&lt;/p&gt;
&lt;p&gt;To help me think about coming back to such disables now and again, I thought
it might be interesting to write a tool that'll show which warnings I
disable most. It resulted in &lt;a href="https://github.com/davep/fish/blob/dd2721e0565928b4145698a42c9c74e4534c578a/conf.d/abbr.d/python.fish#L5" rel="noopener noreferrer" target="_blank"&gt;this fish
abbr&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight" data-lang="sh"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;abbr&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;pylintshame&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rg --no-messages \&amp;quot;pylint:disable=\&amp;quot; | awk &amp;#39;BEGIN{FS=\&amp;quot;disable=\&amp;quot;;}{print \$2}&amp;#39; | tr \&amp;quot;,\&amp;quot; \&amp;quot;\n\&amp;quot; | sort | uniq -c | sort -hr&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The idea here being that it produces a "Pylint hall of shame", something
like this:&lt;/p&gt;
&lt;div class="highlight" data-lang="text"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;  12 wildcard-import
  12 unused-wildcard-import
   8 no-member
   6 invalid-name
   5 no-self-use
   4 import-outside-toplevel
   4 bare-except
   2 unused-argument
   2 too-many-public-methods
   2 too-many-instance-attributes
   2 not-callable
   2 broad-except
   1 wrong-import-position
   1 wrong-import-order
   1 unused-variable
   1 unexpected-keyword-arg
   1 too-many-locals
   1 arguments-differ
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To break the pipeline down:&lt;/p&gt;
&lt;div class="highlight" data-lang="text"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;rg --no-messages &amp;quot;pylint:disable=&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;First off, I use &lt;a href="https://github.com/BurntSushi/ripgrep" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;ripgrep&lt;/code&gt;&lt;/a&gt; (if you
don't, you might want to have a good look at it -- I find it amazingly
handy) to find everywhere in the code in and below the current directory
(the &lt;code&gt;--no-messages&lt;/code&gt; switch just stops any file I/O errors that might result
from permission issues -- they're not interesting here) that contains a line
that has a Pylint block disable (if you tend to format yours differently,
you'll need to tweak the regular expression, of course).&lt;/p&gt;
&lt;p&gt;I then pipe it through &lt;code&gt;awk&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight" data-lang="text"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;awk &amp;#39;BEGIN{FS=&amp;quot;disable=&amp;quot;;}{print $2}&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;so I can lazily &lt;a href="https://docs.freebsd.org/info/gawk/gawk.info.Field_Separators.html" rel="noopener noreferrer" target="_blank"&gt;extract everything after the
&lt;code&gt;disable=&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Next up, because it's a possible list of things that can be disabled, I use
&lt;code&gt;tr&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight" data-lang="text"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tr &amp;quot;,&amp;quot; &amp;quot;\n&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;to turn any comma-separated list into multiple lines.&lt;/p&gt;
&lt;p&gt;Having got to this point, I &lt;code&gt;sort&lt;/code&gt; the list, &lt;code&gt;uniq&lt;/code&gt; the result, while
prepending a count (&lt;code&gt;-c&lt;/code&gt;), and then &lt;code&gt;sort&lt;/code&gt; the result again, in reverse and
sorting the numbers based on how a human would read the result (&lt;code&gt;-hr&lt;/code&gt;).&lt;/p&gt;
&lt;div class="highlight" data-lang="text"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sort | uniq -c | sort -hr
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It's short, sweet and hacky, but does the job quite nicely. From now on, any
time I get curious about which disables I'm leaning on too much, I can use
this to take stock.&lt;/p&gt;</description>
      <guid isPermaLink="false">https://blog.davep.org/2019/11/04/my-pylint-shame.html</guid>
      <category>Python</category>
      <category>Coding</category>
      <category>Python</category>
      <category>fish</category>
      <pubDate>Mon, 04 Nov 2019 20:39:00 +0000</pubDate>
    </item>
    <item>
      <title>pydscheck -- A quick hack that keeps slowly growing</title>
      <link>https://blog.davep.org/2019/10/26/pydscheck.html</link>
      <description>&lt;p&gt;Something I always try to do when I'm coding is be consistent. I feel this
is important. While people's coding standards may differ, I think different
approaches are easier to handle if someone has been consistent with their
style across all of their code.&lt;/p&gt;
&lt;p&gt;This also stands for documentation too.&lt;/p&gt;
&lt;p&gt;In my current position, I do a &lt;em&gt;lot&lt;/em&gt; of Python coding, and one of the things
I like about Python (there are things I don't like too, but that's not for
now) is that it has doc-strings (just like my &lt;a href="https://en.wikipedia.org/wiki/Common_Lisp" rel="noopener noreferrer" target="_blank"&gt;favourite
language&lt;/a&gt;). I use them
extensively, ensuring every function and method has some form of
documentation, and generally I use
&lt;a href="http://www.sphinx-doc.org/en/master/" rel="noopener noreferrer" target="_blank"&gt;Sphinx&lt;/a&gt; to generate documentation
from those doc-strings.&lt;/p&gt;
&lt;p&gt;Early on I was bothered by the fact that, just by the simple act of making
typos, I wasn't keeping the form of the doc-strings consistent. And in this
case it was a &lt;em&gt;really&lt;/em&gt; simple thing that was bugging me. Normally, if I'm
writing a single-line doc-string, I'll write like this:&lt;/p&gt;
&lt;div class="highlight" data-lang="python"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;one_liner&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Here is a one-line doc-string.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So far, so good. But, if the doc-string is a multi-liner, I prefer the
ending quotes to be on a line of their own, like this:&lt;/p&gt;
&lt;div class="highlight" data-lang="python"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;multi_liner&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Here is the first line.&lt;/span&gt;
&lt;span class="sd"&gt;    Here is another line.&lt;/span&gt;
&lt;span class="sd"&gt;    Here is the final line.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;But, sometimes, by accident, I'd leave a doc-string like this:&lt;/p&gt;
&lt;div class="highlight" data-lang="python"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;multi_liner&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Here is the first line.&lt;/span&gt;
&lt;span class="sd"&gt;    Here is another line.&lt;/span&gt;
&lt;span class="sd"&gt;    Here is the final line.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;While it's really not a big deal, it would bug me and every time I found one
like this I'd "fix" it.&lt;/p&gt;
&lt;p&gt;Eventually, it bugged me enough that I decided I was going to write a little
tool to find all such instances in my code and report them. My first
approach was to think "I could just do this with some regexp magic", &lt;a href="http://regex.info/blog/2006-09-15/247" rel="noopener noreferrer" target="_blank"&gt;which
was really a bad idea&lt;/a&gt;. Then I
though, I know, I should use this as an excuse to to play with &lt;a href="https://docs.python.org/3/library/ast.html" rel="noopener noreferrer" target="_blank"&gt;Python's ast
library&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That worked really well! I had &lt;a href="https://github.com/davep/pydscheck/blob/dc5052002690b1f898ccd68f815cdedbe9172b74/pydscheck" rel="noopener noreferrer" target="_blank"&gt;the first version of the
code&lt;/a&gt;
up and running in no time. It was simple but did the job. It ran through
Python code I threw at it and alerted me to both missing doc-strings, and
doc-strings with the ending I didn't like.&lt;/p&gt;
&lt;p&gt;That served me for a while, until one day I realised that it wasn't quite
doing the job correctly; it was only really looking at top-level functions
and top-level methods in classes. Sometimes, not often, but sometimes, I'll
define functions within functions, and I feel they deserve documentation
too. So then I modified the code to ensure it walked every part of the AST.&lt;/p&gt;
&lt;p&gt;Since then, when I've run into new things and had new ideas, &lt;code&gt;pydscheck&lt;/code&gt; has
grown and grown. I've added checks that all mentioned parameters have a
type; I've added checks that any function/method that returns something
actually documents the return value; I've added checks that any
documentation of a returned value includes its type; I've added checks that
any function or method that yields a value documents that fact; I've added
checks that ensure that every parameter is documented in some way.&lt;/p&gt;
&lt;p&gt;Each time I've done this it's helped uncover issues in my code's
documentation that could be cleaner, and it's also given me a pet project to
slowly better understand Python's AST.&lt;/p&gt;
&lt;p&gt;It could be that there are better tools out there, I'd have thought that a
good doc-string linting tool would be something someone had already written.
But this time around I was happy to
&lt;a href="https://en.wikipedia.org/wiki/Not_invented_here" rel="noopener noreferrer" target="_blank"&gt;NIH&lt;/a&gt; it because I needed a
fun learning exercise that would also have some benefits for my day-to-day
work.&lt;/p&gt;
&lt;p&gt;I'll caveat this with the fact that it's very particular to how I work and
how I like my documentation to look, but if it sounds useful, here it is:
&lt;a href="https://github.com/davep/pydscheck" rel="noopener noreferrer" target="_blank"&gt;https://github.com/davep/pydscheck&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There's still lots I could do with it. First off I should really properly
package it up so it can be installed as a command line tool via pip. Other
things that would be handy would be to allow some form of customisation of
how it works. I'm sure there's other fun things I can do with it too.&lt;/p&gt;
&lt;p&gt;That's part of the fun of having a pet project: you can tinker when you like
and also get benefits from it as you use it.&lt;/p&gt;</description>
      <guid isPermaLink="false">https://blog.davep.org/2019/10/26/pydscheck.html</guid>
      <category>Python</category>
      <category>Coding</category>
      <category>Python</category>
      <category>documentation</category>
      <pubDate>Sat, 26 Oct 2019 13:19:00 +0100</pubDate>
    </item>
    <item>
      <title>A little speed issue with openpyxl</title>
      <link>https://blog.davep.org/2018/06/02/a_little_speed_issue_with_openpyxl.html</link>
      <description>&lt;p&gt;It's been very quiet on the blogging front, I'm afraid, mostly for &lt;a href="https://blog.davep.org/2017/12/12/on_to_something_new.html"&gt;the
reasons I wrote about back in December last
year&lt;/a&gt;. In that time I've been really
very busy with work (in a good way, in a &lt;em&gt;very&lt;/em&gt; good way) and there's not a
whole lot of time to be toying with pet projects at home.&lt;/p&gt;
&lt;p&gt;However, finding myself with a spare hour or so, I wanted to write about
something I did run into as part of some development at work, and which I
thought might be worth writing about in case it helps someone else.&lt;/p&gt;
&lt;p&gt;Recently I've needed to write a library of code for loading data from Excel
Workbooks. Given that the vast majority of coding I do at the moment is in
Python, it made sense to make use of
&lt;a href="https://openpyxl.readthedocs.io/" rel="noopener noreferrer" target="_blank"&gt;openpyxl&lt;/a&gt;. The initial prototype code I
wrote worked well and it soon grew into a full-blown library that'll be used
in a couple of work-related projects.&lt;/p&gt;
&lt;p&gt;But one thing kept niggling me... It just wasn't as fast as I'd expected.
The workbooks I'm pulling data from aren't that large, and yet it was taking
a noticeable number of seconds to read in the data, and when I let the code
have a go at a directory full of such workbooks... even the fan on the
machine would ramp up.&lt;/p&gt;
&lt;p&gt;It didn't seem right.&lt;/p&gt;
&lt;p&gt;I did a little bit of profiling and could see that the code was spending
most of its time deep in the guts of some XML-parsing functions. While I
know that an &lt;code&gt;xlsx&lt;/code&gt; file is pretty much an XML document, it seemed odd to me
that it would take so much time and effort to pull the data out from it.&lt;/p&gt;
&lt;p&gt;Given that I had other code to be writing, and given that the
workbook-parsing code was "good enough" for the moment, I moved on for a
short while.&lt;/p&gt;
&lt;p&gt;But, a couple of weeks back, I had a bit of spare time and decided to
revisit it. I did some more searching on openpyxl and speed issues and
almost everything I found said that the common problem was failing to open
the workbook in &lt;code&gt;read_only&lt;/code&gt; mode. That can't have been my problem because
I'd being doing that from the very start.&lt;/p&gt;
&lt;p&gt;Eventually I came across a post somewhere (sorry, I've lost it for now --
I'll try and track it down again) that suggested that openpyxl was very slow
to read from a workbook if you were reading one cell at a time, rather than
using generators. The suggestion being that every time you pull a value form
a cell, it has to parse the whole sheet up to that cell. Generators, on the
other hand, would allow access to all the cells during one parse.&lt;/p&gt;
&lt;p&gt;This seemed a little unlikely to me -- I'd have expected the code to cache
the parsing results or something like that -- but it also would explain what
I was seeing. So I decided to give it a test.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/davep/openpyxl-speed-issue" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;openpyxl-speed-issue&lt;/code&gt;&lt;/a&gt; is a
version of the tests I wrote and ran and they absolutely show that there's a
huge difference between cell-by-cell access vs generator access.&lt;/p&gt;
&lt;p&gt;Code like this:&lt;/p&gt;
&lt;div class="highlight" data-lang="python"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&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="nb"&gt;range&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;sheet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_row&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;col&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&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;sheet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_column&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;][&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;is &lt;em&gt;far slower&lt;/em&gt; than something like this:&lt;/p&gt;
&lt;div class="highlight" data-lang="python"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&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;wb&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Test Sheet&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rows&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;cell&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here's an example of the difference in time, as seen on my iMac:&lt;/p&gt;
&lt;div class="highlight" data-lang="sh"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
pipenv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./read-using-generators
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.59&lt;span class="w"&gt; &lt;/span&gt;real&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.44&lt;span class="w"&gt; &lt;/span&gt;user&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.04&lt;span class="w"&gt; &lt;/span&gt;sys
pipenv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./read-using-peeking
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;25&lt;/span&gt;.02&lt;span class="w"&gt; &lt;/span&gt;real&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;24&lt;/span&gt;.88&lt;span class="w"&gt; &lt;/span&gt;user&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.10&lt;span class="w"&gt; &lt;/span&gt;sys
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As you can see, the cell-by-cell approach is about 16 times slower than the
generator approach.&lt;/p&gt;
&lt;p&gt;In most circumstances the generator approach would make most sense anyway,
and in any other situation I probably would have used it and never have
noticed this. However, the nature of the workbooks I need to pull data from
means I need to "peek ahead" to make decisions about what I'm doing, so a
more traditional loop over, with an index, made more sense.&lt;/p&gt;
&lt;p&gt;I can easily "fix" this by using the generator approach to build up a
two-dimensional array of cells, acquired via the generator; so I can still
do what I want &lt;em&gt;and&lt;/em&gt; benefit from using generators.&lt;/p&gt;
&lt;p&gt;In conclusion: given that I found it difficult to find information about my
speed issue, and given that the one off-hand comment I saw that suggested it
was this wasn't exactly easy to find, I thought I'd write it all down too
and &lt;a href="https://github.com/davep/openpyxl-speed-issue" rel="noopener noreferrer" target="_blank"&gt;create a repository of some test code to illustrate the
issue&lt;/a&gt;. Hopefully someone
else will benefit from this in the future.&lt;/p&gt;</description>
      <guid isPermaLink="false">https://blog.davep.org/2018/06/02/a_little_speed_issue_with_openpyxl.html</guid>
      <category>Python</category>
      <category>Coding</category>
      <category>Python</category>
      <category>openpyxl</category>
      <pubDate>Sat, 02 Jun 2018 13:16:37 +0100</pubDate>
    </item>
  </channel>
</rss>
