<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <id>https://blog.davep.org</id>
  <title>davep</title>
  <updated>2026-08-19T14:27:57.769560+00:00</updated>
  <link href="https://blog.davep.org/feeds/python.atom.xml" rel="self"/>
  <link href="https://blog.davep.org" rel="alternate"/>
  <generator uri="https://lkiesow.github.io/python-feedgen" version="1.0.0">python-feedgen</generator>
  <subtitle>Posts in category "Python" from davep</subtitle>
  <entry>
    <id>https://blog.davep.org/2026/08/16/md2gemtext-a-library-for-converting-markdown-to-gemtext.html</id>
    <title>md2gemtext - A library for converting Markdown to Gemtext</title>
    <updated>2026-08-16T14:43:04+01:00</updated>
    <content type="html">&lt;p&gt;Following on from
&lt;a href="https://blog.davep.org/2026/08/11/html2gemtext-a-simple-library-for-converting-html-to-gemtext.html"&gt;html2gemtext&lt;/a&gt;,
the inevitable has happened: I couldn't let the Markdown side of things
remain unaddressed. While &lt;a href="https://rogallo.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Rogallo&lt;/a&gt;
&lt;a href="https://rogallo.davep.dev/changelog/#v180" rel="noopener noreferrer" target="_blank"&gt;v1.8.0&lt;/a&gt; added some extra
rendering of Markdown to make it look more "pretty", I felt it really needed
to be handled so it had more utility. Being fully navigable, even by
keyboard, seems more important than pretty tables.&lt;/p&gt;
&lt;p&gt;So &lt;a href="https://md2gemtext.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;md2gemtext&lt;/a&gt; now exists as a library on
&lt;a href="https://blog.davep.org/tag/pypi/"&gt;PyPI&lt;/a&gt;. I have two plans for this. While I fully intend to shake
up the Markdown support in Rogallo -- either replacing the use of the
Textual Markdown widget to display it, or at least giving the user a
configuration option to decide how Markdown is displayed -- I also want to
pay some attention to my (currently rather simple) &lt;a href="gemini://tilde.team/~davep/gemlog/" rel="noopener noreferrer" target="_blank"&gt;Gemini-based
log&lt;/a&gt; and use this library to build a
tool that will convert &lt;a href="https://blog.davep.org/"&gt;my blog&lt;/a&gt;'s &lt;a href="https://github.com/davep/davep.github.com/tree/main/content/posts" rel="noopener noreferrer" target="_blank"&gt;Markdown
content&lt;/a&gt;
into Gemtext that looks just how I want it.&lt;/p&gt;
&lt;p&gt;As with html2gemtext, this is an early version of this library, and I'm sure
there will be edge cases I'll have missed and will want to tidy up. But at
the moment the results are looking promising.&lt;/p&gt;
&lt;p&gt;I'm once again in a place where I'm actually half-seriously thinking that
much of what &lt;a href="https://hike.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Hike&lt;/a&gt; does could be built into
Rogallo, if I were to simply add &lt;code&gt;http(s)&lt;/code&gt; support there.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/08/16/md2gemtext-a-library-for-converting-markdown-to-gemtext.html"/>
    <category term="Python"/>
    <category term="Coding"/>
    <category term="Gemini Protocol"/>
    <category term="Markdown"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <category term="gemtext"/>
    <category term="smolweb"/>
    <published>2026-08-16T14:43:04+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/08/16/bagofstuff-v1-3-0.html</id>
    <title>BagOfStuff v1.3.0</title>
    <updated>2026-08-16T09:05:47+01:00</updated>
    <content type="html">&lt;p&gt;For a wee while now I've been trying to nail down a weirdness with the
navigation history in &lt;a href="https://rogallo.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Rogallo&lt;/a&gt;. I never felt it
was a difficult issue to deal with, but it was also one of those issues that
I could never obviously recreate at will, and it only seemed to happen while
I was busy working on something else.&lt;/p&gt;
&lt;p&gt;Yesterday evening &lt;a href="https://github.com/davep/rogallo/issues/290#issuecomment-5304008984" rel="noopener noreferrer" target="_blank"&gt;I finally managed to come up with a way to always
recreate
it&lt;/a&gt;
and, having done so, the solution seems pretty straightforward&lt;sup id="fnref:419-1"&gt;&lt;a class="footnote-ref" href="#fn:419-1"&gt;1&lt;/a&gt;&lt;/sup&gt;. In order
to implement the tweak I want, I need to be able to truncate the navigation
history, and of course do so in a non-destructive way.&lt;/p&gt;
&lt;p&gt;So the history classes in BagOfStuff
&lt;a href="https://bagofstuff.davep.dev/changelog/#v130" rel="noopener noreferrer" target="_blank"&gt;v1.3.0&lt;/a&gt; have grown
&lt;a href="https://bagofstuff.davep.dev/library-contents/history/#bagofstuff.history.SimpleHistory.clone" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;clone&lt;/code&gt;&lt;/a&gt;
and
&lt;a href="https://bagofstuff.davep.dev/library-contents/history/#bagofstuff.history.SimpleHistory.truncate" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;truncate&lt;/code&gt;&lt;/a&gt;
methods. This will allow me to duplicate a history, truncate it at its
current location, then save it, without affecting the working copy.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:419-1"&gt;
&lt;p&gt;Why do I feel like I'll regret saying that?&amp;#160;&lt;a class="footnote-backref" href="#fnref:419-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;</content>
    <link href="https://blog.davep.org/2026/08/16/bagofstuff-v1-3-0.html"/>
    <category term="Python"/>
    <category term="BagOfStuff"/>
    <category term="Coding"/>
    <category term="Python"/>
    <published>2026-08-16T09:05:47+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/08/14/port1900-an-async-python-client-library-for-the-nex-protocol.html</id>
    <title>Port1900 - An async Python client library for the Nex protocol</title>
    <updated>2026-08-14T19:21:42+01:00</updated>
    <content type="html">&lt;p&gt;It's that time again. Time to add another &lt;a href="https://blog.davep.org/tag/smolweb/"&gt;small web
protocol&lt;/a&gt; to &lt;a href="https://rogallo.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Rogallo&lt;/a&gt;. This time
it's one I stumbled across a few days ago: the &lt;a href="https://nightfall.city/nex/info/specification.txt" rel="noopener noreferrer" target="_blank"&gt;Nex
protocol&lt;/a&gt;. This one is
very simple and straightforward and it seemed like a shame &lt;em&gt;not&lt;/em&gt; to support
it.&lt;/p&gt;
&lt;p&gt;So, as I have with &lt;a href="https://wasat.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Gemini&lt;/a&gt;,
&lt;a href="https://sybaritic.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Spartan&lt;/a&gt;, &lt;a href="https://port70.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Gopher&lt;/a&gt;
and &lt;a href="https://port79.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Finger&lt;/a&gt;, I've added a dedicated library for
dealing with the associated URI, and to provide a client for talking to the
server.&lt;/p&gt;
&lt;p&gt;The documentation can be found on &lt;a href="https://port1900.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;the dedicated
site&lt;/a&gt;.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/08/14/port1900-an-async-python-client-library-for-the-nex-protocol.html"/>
    <category term="Python"/>
    <category term="Coding"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <category term="Rogallo"/>
    <category term="Nex Protocol"/>
    <category term="smolweb"/>
    <published>2026-08-14T19:21:42+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/08/12/gophermap-v1-0-0.html</id>
    <title>GopherMap v1.0.0</title>
    <updated>2026-08-12T15:55:27+01:00</updated>
    <content type="html">&lt;p&gt;When I &lt;a href="https://blog.davep.org/2026/07/27/gophermap-a-simple-library-for-parsing-gopher-responses.html"&gt;first created
GopherMap&lt;/a&gt;,
I made it pretty strict about what it did and didn't consider a valid map.
Unsurprisingly, reality did what reality does and, pretty quickly, Rogallo
&lt;a href="https://github.com/davep/rogallo/discussions/241" rel="noopener noreferrer" target="_blank"&gt;met maps that didn't fit with this strict
approach&lt;/a&gt;. So &lt;a href="https://blog.davep.org/2026/07/28/gophermap-v0-2-0.html"&gt;I relaxed
things&lt;/a&gt;, accepting pretty much anything
that even vaguely looked like a Gopher map. This solved the problem.&lt;/p&gt;
&lt;p&gt;However, earlier today, &lt;a href="https://github.com/davep/rogallo/issues/327" rel="noopener noreferrer" target="_blank"&gt;I ran into a page that was obviously supposed to be
plain text, but was being displayed in part like a
map&lt;/a&gt;. The problem was my
attempt at detecting a reply from a Gopher server that looked like it
&lt;em&gt;might&lt;/em&gt; be an error. This very relaxed approach to &lt;em&gt;"this is likely some
Gopher stuff, let's accept it as such"&lt;/em&gt; meant that a text file that had a
&lt;code&gt;3&lt;/code&gt; anywhere in the first column was tripping off the &lt;em&gt;"this is likely some
Gopher stuff and it looks like there's an error in there"&lt;/em&gt; check. The
result? Treated like a Gopher map, converted into Gemtext, and displayed in
the wrong way.&lt;/p&gt;
&lt;p&gt;So... &lt;a href="https://gophermap.davep.dev/changelog/#v100" rel="noopener noreferrer" target="_blank"&gt;v1.0.0&lt;/a&gt; has been
released which adds some code to help out with this. The default parsing
still works as before, but there's now a strict mode which will raise
exceptions if some sort of significant problem is found. On top of this are
a couple of helper class methods which &lt;a href="https://gophermap.davep.dev/gophermap/#gophermap.GopherMap.is_likely_a_map" rel="noopener noreferrer" target="_blank"&gt;check if some text is likely a valid
map&lt;/a&gt;,
and &lt;a href="https://gophermap.davep.dev/gophermap/#gophermap.GopherMap.is_likely_error" rel="noopener noreferrer" target="_blank"&gt;check if some text is likely a valid map containing an
error&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With these in place, I can update Rogallo so that it's still pretty
easy-going when it comes to showing Gopher maps, but isn't quite so
laid-back when it comes to finding errors coming back from Gopher servers,
and so incorrectly rendering the result.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/08/12/gophermap-v1-0-0.html"/>
    <category term="Python"/>
    <category term="Coding"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <category term="gopher"/>
    <category term="smolweb"/>
    <published>2026-08-12T15:55:27+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/08/11/html2gemtext-a-simple-library-for-converting-html-to-gemtext.html</id>
    <title>html2gemtext - A simple library for converting HTML to Gemtext</title>
    <updated>2026-08-11T20:24:39+01:00</updated>
    <content type="html">&lt;p&gt;Yesterday evening, while tinkering with
&lt;a href="https://rogallo.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Rogallo&lt;/a&gt;, I managed to nerd-snipe myself pretty
well. I was playing around with the idea of better presentation of some
forms of &lt;code&gt;text/*&lt;/code&gt; content. While, of course, Rogallo handles &lt;code&gt;text/gemtext&lt;/code&gt;
just fine, there are lots of other MIME types that it will show too (pretty
much all within &lt;code&gt;text/*&lt;/code&gt;). Already, if it can work out an appropriate
language, the viewer will do syntax highlighting, and this includes Markdown
files. However, &lt;a href="https://textual.textualize.io/widgets/markdown/" rel="noopener noreferrer" target="_blank"&gt;Rogallo has a full Markdown widget built right
in&lt;/a&gt; so I was experimenting
with using that as the way to show Markdown content.&lt;/p&gt;
&lt;p&gt;Nothing that clever really, all pretty obvious.&lt;/p&gt;
&lt;p&gt;But then I got to thinking... In &lt;a href="https://oldnews.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;OldNews&lt;/a&gt; I
heavily rely on
&lt;a href="https://docs.html-to-markdown.xberg.io/" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;html-to-markdown&lt;/code&gt;&lt;/a&gt;. Given I have
a Markdown viewer to hand, if the user ends up trying to look at some
&lt;code&gt;text/html&lt;/code&gt;, why not convert it over to Markdown and render it that way?
That's... doable.&lt;/p&gt;
&lt;p&gt;There are, however, some problems with this idea:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;As useful as &lt;code&gt;html-to-markdown&lt;/code&gt; has been for OldNews, I've found it quite
  unreliable at times, with the occasional breaking change. I don't mention
  this as a negative about the project, but I don't want another one of my
  projects sitting on top of a moving target like that.&lt;/li&gt;
&lt;li&gt;Dragging in a reasonably large dependency for what's likely to be a niche
  requirement doesn't quite feel right.&lt;/li&gt;
&lt;li&gt;Textual's Markdown viewer has, to this day, one massive design flaw: links
  can't be navigated with the keyboard&lt;sup id="fnref:424-1"&gt;&lt;a class="footnote-ref" href="#fn:424-1"&gt;1&lt;/a&gt;&lt;/sup&gt;. I'm doing my absolute best to
  ensure that Rogallo is keyboard-first. Leaning on this widget for viewing
  Markdown is one thing, but leaning on the widget for other content types
  starts to erode that aim.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I did consider the idea of having this feature as an install option, so
you'd be able to install &lt;code&gt;rogallo&lt;/code&gt; and have it work as normal, or install
&lt;code&gt;rogallo[html]&lt;/code&gt; (or similar) and it would drag in the ability to render some
HTML using this pipeline.&lt;/p&gt;
&lt;p&gt;Then I realised: why target Markdown at all? I already have the code for
rendering Gemtext, and that solves the keyboard navigation of links problem.
How hard could it be to write something to convert HTML to Gemtext?&lt;/p&gt;
&lt;p&gt;Turns out, at least for the requirements I have right now, &lt;a href="https://github.com/davep/html2gemtext" rel="noopener noreferrer" target="_blank"&gt;not that
hard&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://html2gemtext.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;html2gemtext&lt;/code&gt;&lt;/a&gt; is still in its infancy,
but it's doing a passable job of turning most of the HTML I throw at it into
reasonable Gemtext. I don't doubt for a moment that there are &lt;em&gt;lots&lt;/em&gt; of
pages out there that won't turn out right -- for varied values of right --
but so far the results are readable and navigable. The plan now is to keep
improving as I run into new cases that could be handled better.&lt;/p&gt;
&lt;p&gt;While I've not built this into Rogallo just yet, I think I will make use of
it. I'm also giving serious thought to using it for &lt;a href="gemini://tilde.team/~davep/" rel="noopener noreferrer" target="_blank"&gt;my
capsule&lt;/a&gt;. Over there I'm &lt;a href="gemini://tilde.team/~davep/gemlog/" rel="noopener noreferrer" target="_blank"&gt;adding some posts
from this blog&lt;/a&gt; and &lt;code&gt;html2gemtext&lt;/code&gt; could
form the basis of a tool to help automate some of that.&lt;/p&gt;
&lt;p&gt;Right now &lt;a href="https://github.com/davep/tilde-team-capsule/blob/11a6bfa7e0ab6015471d660cacdd0287d10bd3dc/bin/borrow-from-blog" rel="noopener noreferrer" target="_blank"&gt;I'm using a script I've
written&lt;/a&gt;
that wraps &lt;a href="https://kristaps.bsd.lv/lowdown/" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;lowdown&lt;/code&gt;&lt;/a&gt; to do the Markdown
to Gemtext conversion, but the result -- as good as it is -- isn't &lt;em&gt;quite&lt;/em&gt;
what I'd like. I'm thinking I could tailor the results exactly as I want if
I go from this blog's HTML via &lt;code&gt;html2gemtext&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Or, of course, given my blog is written in Markdown, I could next tackle my
own just-how-I-need-it Markdown to Gemtext converter...&lt;/p&gt;
&lt;p&gt;As for how this new library will go into Rogallo... Using it to render HTML
that might be found kicking about in Geminispace or Gopherspace makes sense,
I think. That should be enough. It's not like I &lt;em&gt;really&lt;/em&gt; need to turn it
into an &lt;code&gt;http(s)&lt;/code&gt; browser too. Right? Right?!?&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:424-1"&gt;
&lt;p&gt;This mouse-only problem is a recurring theme in Textual.&amp;#160;&lt;a class="footnote-backref" href="#fnref:424-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;</content>
    <link href="https://blog.davep.org/2026/08/11/html2gemtext-a-simple-library-for-converting-html-to-gemtext.html"/>
    <category term="Python"/>
    <category term="Coding"/>
    <category term="Gemini Protocol"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <category term="gemtext"/>
    <category term="smolweb"/>
    <published>2026-08-11T20:24:39+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/08/07/sybaritic-a-spartan-protocol-library-for-python.html</id>
    <title>Sybaritic - A Spartan protocol library for Python</title>
    <updated>2026-08-07T19:27:00+01:00</updated>
    <content type="html">&lt;p&gt;Now that &lt;a href="https://rogallo.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Rogallo&lt;/a&gt; supports &lt;a href="https://rogallo.davep.dev/protocols/" rel="noopener noreferrer" target="_blank"&gt;multiple
protocols&lt;/a&gt;, it's tempting to add one
or two more. As of the time of writing, alongside
&lt;a href="https://rogallo.davep.dev/gemini/" rel="noopener noreferrer" target="_blank"&gt;Gemini&lt;/a&gt;, both
&lt;a href="https://rogallo.davep.dev/gopher/" rel="noopener noreferrer" target="_blank"&gt;Gopher&lt;/a&gt; and
&lt;a href="https://rogallo.davep.dev/finger/" rel="noopener noreferrer" target="_blank"&gt;Finger&lt;/a&gt; are supported. While wandering
the &lt;a href="https://blog.davep.org/tag/smolweb/"&gt;small web&lt;/a&gt;, I stumbled on &lt;a href="https://github.com/michael-lazar/spartan" rel="noopener noreferrer" target="_blank"&gt;the Spartan
protocol&lt;/a&gt;. Given its similarity to
Gemini and the fact it uses &lt;a href="https://blog.davep.org/tag/gemtext/"&gt;Gemtext&lt;/a&gt; as its main document
type, adding it to Rogallo seemed like an obvious thing to do.&lt;/p&gt;
&lt;p&gt;In aid of this, I've created &lt;a href="https://sybaritic.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Sybaritic&lt;/a&gt;. It
is, in effect, a sibling library to &lt;a href="https://wasat.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Wasat&lt;/a&gt;,
&lt;a href="https://port70.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Port70&lt;/a&gt; and &lt;a href="https://port79.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Port79&lt;/a&gt;.
As with the others, it is async-all-the-way and is designed to have a
similar API.&lt;/p&gt;
&lt;p&gt;With this in place, I can now extend Rogallo to handle &lt;code&gt;spartan://&lt;/code&gt; URIs as
first-class citizens.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/08/07/sybaritic-a-spartan-protocol-library-for-python.html"/>
    <category term="Python"/>
    <category term="Coding"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <category term="Rogallo"/>
    <category term="Spartan Protocol"/>
    <category term="smolweb"/>
    <published>2026-08-07T19:27:00+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/08/07/gemtext-v1-1-0.html</id>
    <title>Gemtext v1.1.0</title>
    <updated>2026-08-07T16:33:03+01:00</updated>
    <content type="html">&lt;p&gt;I've released a small update to &lt;a href="https://gemtext.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Gemtext&lt;/a&gt;. At
the moment, I'm working on adding &lt;a href="https://portal.mozz.us/spartan/spartan.mozz.us/" rel="noopener noreferrer" target="_blank"&gt;Spartan
protocol&lt;/a&gt; support to
&lt;a href="https://rogallo.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Rogallo&lt;/a&gt;, and to do this I need to handle a
small extension it makes to Gemtext.&lt;/p&gt;
&lt;p&gt;While Spartan is quite different from Gemini in the underlying protocol, it
uses Gemtext as the default/standard markup language, but with one small
difference. To allow uploaded data that is initiated by the markup, rather
than by the server, there is a &lt;code&gt;=:&lt;/code&gt; line type. In some respects, this is
similar to type &lt;code&gt;7&lt;/code&gt; items in Gopher maps.&lt;/p&gt;
&lt;p&gt;Rather than spin up a whole new library just to support this one small
difference, and rather than do some special-case nonsense in Rogallo itself,
I've added optional (and turned on by default) support for &lt;code&gt;=:&lt;/code&gt;. When
encountered, this results in a
&lt;a href="https://gemtext.davep.dev/gemtext/#gemtext.SpartanPrompt" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;SpartanPrompt&lt;/code&gt;&lt;/a&gt;
object, which simply inherits from a
&lt;a href="https://gemtext.davep.dev/gemtext/#gemtext.Link" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;Link&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Any code that doesn't require this feature can turn it off with the
&lt;a href="https://gemtext.davep.dev/gemtext/#gemtext.Gemtext(with_spartan_support)" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;with_spartan_support&lt;/code&gt;
parameter&lt;/a&gt;.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/08/07/gemtext-v1-1-0.html"/>
    <category term="Python"/>
    <category term="Coding"/>
    <category term="Spartan Protocol"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <category term="gemtext"/>
    <category term="smolweb"/>
    <published>2026-08-07T16:33:03+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/07/30/wasat-v1-3-0.html</id>
    <title>Wasat v1.3.0</title>
    <updated>2026-07-30T16:04:09+01:00</updated>
    <content type="html">&lt;p&gt;A quick bump of &lt;a href="https://wasat.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Wasat&lt;/a&gt; to
&lt;a href="https://wasat.davep.dev/changelog/#v130" rel="noopener noreferrer" target="_blank"&gt;v1.3.0&lt;/a&gt;. This follows on from the
work &lt;a href="https://blog.davep.org/2026/07/30/wasat-v1-2-0.html"&gt;started in v1.2.0&lt;/a&gt;, improving how
hybrid mode works and also making host certificate verification information
available in the response object.&lt;/p&gt;
&lt;p&gt;The main improvement to hybrid mode is that it's now a little more
discerning about when to fall back on TOFU mode. &lt;em&gt;Only&lt;/em&gt; if the CA route
fails due to an untrusted root or a self-signed certificate does it then
fall back to TOFU; otherwise, an exception is raised.&lt;/p&gt;
&lt;p&gt;I've also added verification information -- such as the method used and the
fingerprint -- to the response object. This will be useful in
&lt;a href="https://blog.davep.org/tag/rogallo/"&gt;Rogallo&lt;/a&gt; if I want to show in the display how the current
server was verified.&lt;/p&gt;
&lt;p&gt;As a handy diagnostic tool, I've also added the verification method and the
fingerprint to the verbose output of the library's CLI command.&lt;/p&gt;
&lt;div class="highlight" data-lang="text"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ wasat --verify-mode=hybrid -v gemini://astrobotany.mozz.us/ | head -7
--- Gemini Response ---
URI: gemini://astrobotany.mozz.us/
Verification Method: ca
Certificate Fingerprint: sha256:dafe13d51b1aff133dd153c6d66a1a15761020017daa69378aef6e0eefb75474
Status: 20 (SUCCESS)
Meta: text/gemini
-----------------------

$ wasat --verify-mode=hybrid -v gemini://tilde.team/~davep/ | head -7
--- Gemini Response ---
URI: gemini://tilde.team/~davep/
Verification Method: tofu
Certificate Fingerprint: sha256:239f2642895698fbd16bd6fc59f2361caf6b7449a37d861d86936978c175bf78
Status: 20 (SUCCESS)
Meta: text/gemini; lang=en
-----------------------
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With these changes in place, I think I'm set for making some improvements to
Rogallo relating to host certificates.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/07/30/wasat-v1-3-0.html"/>
    <category term="Python"/>
    <category term="Coding"/>
    <category term="Gemini Protocol"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <category term="smolweb"/>
    <category term="wasat"/>
    <published>2026-07-30T16:04:09+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/07/30/wasat-v1-2-0.html</id>
    <title>Wasat v1.2.0</title>
    <updated>2026-07-30T08:45:09+01:00</updated>
    <content type="html">&lt;p&gt;I've just released &lt;a href="https://wasat.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Wasat&lt;/a&gt;
&lt;a href="https://wasat.davep.dev/changelog/#v120" rel="noopener noreferrer" target="_blank"&gt;v1.2.0&lt;/a&gt;. This release aims to help
out with a change I want to make in &lt;a href="https://blog.davep.org/tag/rogallo/"&gt;Rogallo&lt;/a&gt; in relation to
certificate verification.&lt;/p&gt;
&lt;p&gt;The issue is that, until now, Wasat offered either &lt;code&gt;ca&lt;/code&gt; or &lt;code&gt;tofu&lt;/code&gt; as
verification modes. Meanwhile Rogallo was using &lt;code&gt;tofu&lt;/code&gt;. Long story short:
&lt;a href="https://github.com/davep/rogallo/issues/221" rel="noopener noreferrer" target="_blank"&gt;this meant that sometimes some sites looked like they were changing
fingerprint way more often than you'd
expect&lt;/a&gt;. The obvious solution
here is to first check if a site can be validated via the &lt;code&gt;ca&lt;/code&gt; route and, if
not, fall back to the &lt;code&gt;tofu&lt;/code&gt; route. This sort of change could have been made
in Rogallo itself, but it would have been inelegant. Rogallo maintains a
single Gemini client object, and the verification mode is baked in when the
object is created. To move to this &lt;em&gt;"try one way then the other"&lt;/em&gt; approach
would have meant either maintaining two instances of the object, or tearing
it down and making another each time we connected to a capsule.&lt;/p&gt;
&lt;p&gt;Nah.&lt;/p&gt;
&lt;p&gt;So the
&lt;a href="https://wasat.davep.dev/library/client/#wasat.client.Client(verify_mode)" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;verify_mode&lt;/code&gt;&lt;/a&gt;
of the
&lt;a href="https://wasat.davep.dev/library/client/#wasat.client.Client" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;Client&lt;/code&gt;&lt;/a&gt; has
grown a new option: &lt;code&gt;hybrid&lt;/code&gt;. When set to &lt;code&gt;hybrid&lt;/code&gt;, the client will do the
dance mentioned above: it will try and see if &lt;code&gt;ca&lt;/code&gt; will work and, if it
does, it will take that approach. If it fails, it will then fall back on
pure &lt;code&gt;tofu&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Out of the box, the defaults for the client are the same (it uses &lt;code&gt;ca&lt;/code&gt; by
default), and for the moment Rogallo is still hard-baked to &lt;code&gt;tofu&lt;/code&gt;; given
this, there should be no change in how things work when Wasat is updated.
Now that this is up on PyPI, I'm going to pin Rogallo's use of &lt;code&gt;wasat&lt;/code&gt; to
&lt;code&gt;&amp;gt;=1.2.0&lt;/code&gt; and then, in an update, I'll swap over to &lt;code&gt;hybrid&lt;/code&gt;. Hopefully this
will result in a smoother journey when using sites like
&lt;a href="gemini://astrobotany.mozz.us/app" rel="noopener noreferrer" target="_blank"&gt;AstroBotany&lt;/a&gt; and
&lt;a href="gemini://station.martinrue.com/" rel="noopener noreferrer" target="_blank"&gt;Station&lt;/a&gt;.&lt;/p&gt;
&lt;div class="admonition admonition-note"&gt;
&lt;div class="admonition-title"&gt;ℹ️ Note&lt;/div&gt;
&lt;div class="admonition-content"&gt;
&lt;p&gt;While writing this, I've noticed that there's one change I still need to
make to this hybrid flow. I realised that, at the moment, &lt;em&gt;any&lt;/em&gt; kind of CA
failure will result in a fallback to TOFU. That's not ideal and needs
refining. All part of the learning process.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;PS: If you're wondering what happened to v1.1.0 of Wasat... there wasn't
one. While preparing this release, I was also fighting with the fact that my
MX Mechanical Mini keyboard seemed to be dying (and its battery &lt;em&gt;does&lt;/em&gt; seem
to be dying) and, because of that, managed to bump the version from v1.0.1
to v1.2.0. Oh well...&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/07/30/wasat-v1-2-0.html"/>
    <category term="Python"/>
    <category term="Coding"/>
    <category term="Gemini Protocol"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <category term="smolweb"/>
    <category term="wasat"/>
    <published>2026-07-30T08:45:09+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/07/28/gophermap-v0-2-0.html</id>
    <title>GopherMap v0.2.0</title>
    <updated>2026-07-28T16:48:01+01:00</updated>
    <content type="html">&lt;p&gt;A quick little update to &lt;a href="https://gophermap.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;GopherMap&lt;/a&gt;.
&lt;a href="https://gophermap.davep.dev/changelog/#v020" rel="noopener noreferrer" target="_blank"&gt;v0.2.0&lt;/a&gt; greatly relaxes the
validation of incoming lines, being a lot less fussy about missing tab
characters and also about fully empty lines.&lt;/p&gt;
&lt;p&gt;In the first take on this I was being pretty strict but, unsurprisingly,
&lt;a href="https://github.com/davep/rogallo/discussions/241" rel="noopener noreferrer" target="_blank"&gt;there are sites out there with likely-malformed
maps&lt;/a&gt; that should
otherwise work if you're chill about their mistakes.&lt;/p&gt;
&lt;p&gt;I've got no desire for &lt;a href="https://blog.davep.org/tag/rogallo/"&gt;Rogallo&lt;/a&gt; to be harsh in this respect,
so here's a much more relaxed version of GopherMap.&lt;/p&gt;
&lt;p&gt;As of right now, the exceptions that are defined in the library are never
raised. I've left them in for the moment because there's a window of time
between this version being pushed to PyPI and Rogallo being updated (it does
use the exceptions), where failure to supply them would cause Rogallo to
crash under normal circumstances (nobody wants an unnecessary
&lt;code&gt;ImportError&lt;/code&gt;). Once I've updated Rogallo to pin a new lower bound, and
removed use of the exceptions, I'll remove them from GopherMap.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/07/28/gophermap-v0-2-0.html"/>
    <category term="Python"/>
    <category term="Coding"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <category term="gopher"/>
    <category term="smolweb"/>
    <published>2026-07-28T16:48:01+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/07/27/gophermap-a-simple-library-for-parsing-gopher-responses.html</id>
    <title>GopherMap - A simple library for parsing Gopher responses</title>
    <updated>2026-07-27T19:20:30+01:00</updated>
    <content type="html">&lt;p&gt;Having &lt;a href="https://blog.davep.org/2026/07/26/port70-a-gopher-protocol-library-for-python.html"&gt;spun up
Port70&lt;/a&gt; as a
support library for my effort to add &lt;a href="https://blog.davep.org/tag/gopher/"&gt;gopher&lt;/a&gt; support to
&lt;a href="https://blog.davep.org/tag/rogallo/"&gt;Rogallo&lt;/a&gt;, the next thing I needed was some code to parse the
"gopher map" responses that you get back from Gopher servers. While this
could have been some code in Rogallo itself, much like with
&lt;a href="https://gemtext.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Gemtext&lt;/a&gt;, it seemed sensible to put it in its
own library so it could be useful elsewhere.&lt;/p&gt;
&lt;p&gt;So... &lt;a href="https://gophermap.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;GopherMap&lt;/a&gt;
&lt;a href="https://gophermap.davep.dev/changelog/#v010" rel="noopener noreferrer" target="_blank"&gt;v0.1.0&lt;/a&gt; is a thing. Its main
provision is a &lt;a href="https://gophermap.davep.dev/gophermap/#gophermap.GopherMap" rel="noopener noreferrer" target="_blank"&gt;class called
&lt;code&gt;GopherMap&lt;/code&gt;&lt;/a&gt;,
which is used to parse some text you give it. Its &lt;a href="https://gophermap.davep.dev/gophermap/#gophermap.GopherMap.items" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;items&lt;/code&gt;
property&lt;/a&gt;
is then a tuple of
&lt;a href="https://gophermap.davep.dev/gophermap/#gophermap.GopherItem" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;GopherItem&lt;/code&gt;&lt;/a&gt;
objects. Each one of those objects has &lt;a href="https://gophermap.davep.dev/gophermap/#gophermap.GopherItem.type" rel="noopener noreferrer" target="_blank"&gt;a &lt;code&gt;type&lt;/code&gt;
property&lt;/a&gt;
which can be used to &lt;a href="https://gophermap.davep.dev/gophermap/#gophermap.ItemType" rel="noopener noreferrer" target="_blank"&gt;check the type of the
resource&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Because Rogallo heavily relies on MIME types to make decisions about what to
do with content, each &lt;code&gt;ItemType&lt;/code&gt; has a best-efforts-guess
&lt;a href="https://gophermap.davep.dev/gophermap/#gophermap.ItemType.mime_type" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;mime_type&lt;/code&gt;&lt;/a&gt;
associated with it. This will make it pretty straightforward for me to
decide if a resource from a Gopher server is something Rogallo can display.&lt;/p&gt;
&lt;p&gt;All of this has prompted me to &lt;a href="gopher://tilde.team/1/~davep/" rel="noopener noreferrer" target="_blank"&gt;have a go at creating my own little Gopher
site&lt;/a&gt; to help with testing. As with my &lt;a href="gemini://tilde.team/~davep/" rel="noopener noreferrer" target="_blank"&gt;wee
Gemini capsule&lt;/a&gt;, this is all thanks to
&lt;a href="https://tilde.team/" rel="noopener noreferrer" target="_blank"&gt;tilde.team&lt;/a&gt;.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/07/27/gophermap-a-simple-library-for-parsing-gopher-responses.html"/>
    <category term="Python"/>
    <category term="Coding"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <category term="gopher"/>
    <category term="smolweb"/>
    <published>2026-07-27T19:20:30+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/07/26/port70-a-gopher-protocol-library-for-python.html</id>
    <title>Port70 - A Gopher protocol library for Python</title>
    <updated>2026-07-26T11:32:33+01:00</updated>
    <content type="html">&lt;p&gt;It was, of course, inevitable that this would happen. After &lt;a href="https://blog.davep.org/2026/07/25/port79-v1-0-0.html"&gt;spinning up
Port79&lt;/a&gt; so that I could &lt;a href="https://blog.davep.org/2026/07/24/rogallo-v1-1-0.html"&gt;add &lt;code&gt;finger&lt;/code&gt;
support to Rogallo&lt;/a&gt;, it made sense that I
start to think about Gopher support too. So here we go:
&lt;a href="https://port70.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Port70&lt;/a&gt;, a similar library for async interaction
with Gopher servers.&lt;/p&gt;
&lt;p&gt;This is going to be a bit of an adventure for me. While I'm long familiar
with the existence of Gopher, and I'm fairly certain I used a client once or
twice back in the 90s, I've never really had dealings with the protocol so
know very little about it. Which is a good thing: something new (old?) to
learn.&lt;/p&gt;
&lt;p&gt;My plan with this is to add the &lt;code&gt;GopherURI&lt;/code&gt; class to &lt;a href="https://github.com/davep/rogallo/blob/7ea4abaddb542e0cc9baa5ddb88d24836cf2bdec/src/rogallo/types.py#L17-L19" rel="noopener noreferrer" target="_blank"&gt;the list of URI
types&lt;/a&gt;
that &lt;a href="https://github.com/davep/rogallo/blob/7ea4abaddb542e0cc9baa5ddb88d24836cf2bdec/src/rogallo/screens/main.py#L711-L723" rel="noopener noreferrer" target="_blank"&gt;Rogallo understands and
handles&lt;/a&gt;
and then provide a method of displaying and navigating the result. I &lt;em&gt;think&lt;/em&gt;
the cleanest and easiest way of doing this will be to add some code that
transforms a &lt;a href="https://en.wikipedia.org/wiki/Gopher_(protocol)#Source_code_of_a_menu" rel="noopener noreferrer" target="_blank"&gt;menu
response&lt;/a&gt;
into &lt;a href="https://gemtext.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;gemtext&lt;/a&gt; and then just let the &lt;a href="https://github.com/davep/rogallo/tree/main/src/rogallo/widgets/viewer" rel="noopener noreferrer" target="_blank"&gt;viewer
widget&lt;/a&gt;
display it as normal; each of the links in the menu being turned into
&lt;code&gt;gopher://&lt;/code&gt; URIs.&lt;/p&gt;
&lt;p&gt;This should be fun to play with. I'm looking forward to pulling it all
together.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/07/26/port70-a-gopher-protocol-library-for-python.html"/>
    <category term="Python"/>
    <category term="Coding"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <category term="Rogallo"/>
    <category term="gopher"/>
    <category term="smolweb"/>
    <published>2026-07-26T11:32:33+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/07/25/port79-v1-0-0.html</id>
    <title>Port79 v1.0.0</title>
    <updated>2026-07-25T14:25:04+01:00</updated>
    <content type="html">&lt;p&gt;I've just bumped &lt;a href="https://port79.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Port79&lt;/a&gt; to
&lt;a href="https://port79.davep.dev/changelog/#v100" rel="noopener noreferrer" target="_blank"&gt;v1.0.0&lt;/a&gt;. There's no significant
change in this release, other than the addition of the usual dunder-based
metadata that is common in Python libraries; somehow I'd managed to leave it
out in the last couple of releases.&lt;/p&gt;
&lt;p&gt;Now that I've successfully &lt;a href="https://blog.davep.org/2026/07/24/rogallo-v1-1-0.html"&gt;added &lt;code&gt;finger&lt;/code&gt; support to
Rogallo&lt;/a&gt;, and the library is working out
well, there didn't seem much point in letting it hang around in
zero-point-whatever-land.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/07/25/port79-v1-0-0.html"/>
    <category term="Python"/>
    <category term="Coding"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <category term="finger"/>
    <category term="port79"/>
    <category term="smolweb"/>
    <published>2026-07-25T14:25:04+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/07/23/port79-v0-2-0.html</id>
    <title>Port79 v0.2.0</title>
    <updated>2026-07-23T11:04:54+01:00</updated>
    <content type="html">&lt;p&gt;A quick update to &lt;a href="https://port79.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Port79&lt;/a&gt;. When I kicked this
off I'd set up the &lt;a href="https://port79.davep.dev/library/uri/#port79.uri.FingerURI" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;FingerURI&lt;/code&gt;
class&lt;/a&gt; so that
it was pretty permissive in what it would accept. So, for example, you could
do any of:&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="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;FingerURI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;davep&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;FingerURI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;finger://davep/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;FingerURI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;davep@example.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;FingerURI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;finger://example.com/davep&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;FingerURI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;finger://example.com/davep&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;FingerURI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;finger://example.com/davep&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Nice and handy, right? Accept pretty much any input and turn it into a
&lt;code&gt;finger&lt;/code&gt; URI. However, once I started to add finger support to
&lt;a href="https://blog.davep.org/tag/rogallo/"&gt;Rogallo&lt;/a&gt;, I realised that this wasn't as helpful as I'd like
as it meant I couldn't use it to actually check for a valid URI. It would be
more useful to have the class be strict about &lt;em&gt;only&lt;/em&gt; being passed &lt;code&gt;finger:&lt;/code&gt;
URIs.&lt;/p&gt;
&lt;p&gt;So now, the first two examples give an error.&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="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;FingerURI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;davep&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;port79&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URIError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Invalid&lt;/span&gt; &lt;span class="n"&gt;URI&lt;/span&gt; &lt;span class="n"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Expected&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;finger&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;FingerURI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;davep@example.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;port79&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URIError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Invalid&lt;/span&gt; &lt;span class="n"&gt;URI&lt;/span&gt; &lt;span class="n"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Expected&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;finger&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Instead, if you want the more relaxed approach, you should use the
&lt;code&gt;from_string&lt;/code&gt; method.&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="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;FingerURI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;davep&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;FingerURI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;finger://davep/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;FingerURI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;davep@example.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;FingerURI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;finger://example.com/davep&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With this change I can confidently use &lt;code&gt;FingerURI&lt;/code&gt; to test if any given
input is an actual finger URI, and this also makes it work in a similar way
to &lt;a href="https://wasat.davep.dev/library/uri/#wasat.uri.GeminiURI" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;GeminiURI&lt;/code&gt;&lt;/a&gt;
in &lt;a href="https://wasat.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Wasat&lt;/a&gt;.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/07/23/port79-v0-2-0.html"/>
    <category term="Python"/>
    <category term="Coding"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <category term="finger"/>
    <category term="port79"/>
    <category term="smolweb"/>
    <published>2026-07-23T11:04:54+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/07/21/wasat-v1-0-0.html</id>
    <title>Wasat v1.0.0</title>
    <updated>2026-07-21T19:10:21+01:00</updated>
    <content type="html">&lt;p&gt;While I'm having a moment of &lt;a href="https://blog.davep.org/2026/07/21/rogallo-v1-0-0.html"&gt;calling code
stable&lt;/a&gt;, I've bumped
&lt;a href="https://wasat.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Wasat&lt;/a&gt; to
&lt;a href="https://wasat.davep.dev/changelog/#v100" rel="noopener noreferrer" target="_blank"&gt;v1.0.0&lt;/a&gt; as well. There's no actual
difference here when compared to the &lt;a href="https://blog.davep.org/2026/07/18/wasat-v0-8-0.html"&gt;last
release&lt;/a&gt;, other than the development status
classifier in the PyPI metadata and the version number.&lt;/p&gt;
&lt;p&gt;The library feels pretty stable at this point. I've been using
&lt;a href="https://blog.davep.org/tag/rogallo/"&gt;Rogallo&lt;/a&gt; every day for a wee while now and I've seen no
surprising problems.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/07/21/wasat-v1-0-0.html"/>
    <category term="Python"/>
    <category term="Coding"/>
    <category term="Gemini Protocol"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <category term="smolweb"/>
    <category term="wasat"/>
    <published>2026-07-21T19:10:21+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/07/21/port79-a-finger-protocol-library-for-python.html</id>
    <title>Port79 - A finger protocol library for Python</title>
    <updated>2026-07-21T16:19:20+01:00</updated>
    <content type="html">&lt;p&gt;Now that &lt;a href="https://rogallo.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Rogallo&lt;/a&gt; is starting to settle down
somewhat, with all the main &lt;a href="https://blog.davep.org/tag/gemini-protocol/"&gt;Gemini Protocol&lt;/a&gt; work
falling into place, I've been thinking about adding one or two adjacent
protocols. The two most common and obvious ones are
&lt;a href="https://en.wikipedia.org/wiki/Finger_(protocol)" rel="noopener noreferrer" target="_blank"&gt;Finger&lt;/a&gt; and
&lt;a href="https://en.wikipedia.org/wiki/Gopher_(protocol)" rel="noopener noreferrer" target="_blank"&gt;Gopher&lt;/a&gt;. While I'm still
undecided about adding the latter, the former seems like a fun one to
include (if only because &lt;a href="finger://plan.cat/davep" rel="noopener noreferrer" target="_blank"&gt;I've had an account&lt;/a&gt; on
&lt;a href="https://plan.cat/" rel="noopener noreferrer" target="_blank"&gt;plan.cat&lt;/a&gt; for around four years now).&lt;/p&gt;
&lt;p&gt;With that in mind, much like &lt;a href="https://blog.davep.org/2026/06/17/wasat-a-gemini-protocol-library-for-python.html"&gt;I did with
Wasat&lt;/a&gt;, I wrote
a spec for what I wanted and pointed &lt;a href="https://blog.davep.org/tag/antigravity/"&gt;Antigravity&lt;/a&gt; at it.
The result is &lt;a href="https://port79.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Port79&lt;/a&gt;. As well as providing a
library, there's also a small &lt;code&gt;finger&lt;/code&gt; clone CLI, which can be run with
&lt;code&gt;python -m port79&lt;/code&gt; (if it's installed as a library) or with &lt;code&gt;port79&lt;/code&gt; (if
installed along with any command scripts).&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;port79&lt;span class="w"&gt; &lt;/span&gt;davep@tilde.team

hello&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;removed&lt;span class="o"&gt;]&lt;/span&gt;,

Project:
&lt;span class="w"&gt;  &lt;/span&gt;Rogallo:&lt;span class="w"&gt; &lt;/span&gt;https://rogallo.davep.dev/

Plan:
&lt;span class="w"&gt;  &lt;/span&gt;Currently&lt;span class="w"&gt; &lt;/span&gt;building&lt;span class="w"&gt; &lt;/span&gt;gemini://tilde.team/~davep/

Pronouns:&lt;span class="w"&gt; &lt;/span&gt;he/him

$&lt;span class="w"&gt; &lt;/span&gt;port79&lt;span class="w"&gt; &lt;/span&gt;davep@plan.cat
Login:&lt;span class="w"&gt; &lt;/span&gt;davep&lt;span class="w"&gt;                            &lt;/span&gt;Name:&lt;span class="w"&gt; &lt;/span&gt;Dave&lt;span class="w"&gt; &lt;/span&gt;Pearson
Directory:&lt;span class="w"&gt; &lt;/span&gt;/home/davep&lt;span class="w"&gt;                 &lt;/span&gt;Shell:&lt;span class="w"&gt; &lt;/span&gt;/bin/plan.cat
Last&lt;span class="w"&gt; &lt;/span&gt;login&lt;span class="w"&gt; &lt;/span&gt;Mon&lt;span class="w"&gt; &lt;/span&gt;Jul&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;:06:33&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2026&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;UTC
No&lt;span class="w"&gt; &lt;/span&gt;Mail.
Plan:
Rogallo&lt;span class="w"&gt; &lt;/span&gt;v0.12.0,&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;some&lt;span class="w"&gt; &lt;/span&gt;more&lt;span class="w"&gt; &lt;/span&gt;QoL&lt;span class="w"&gt; &lt;/span&gt;changes:
https://blog.davep.org/2026/07/20/rogallo-v0-12-0.html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To be clear: this isn't intended to be a serious finger command (like, why
would anyone even need that?), it's just a tool within the library that can
be used to do some testing within a development (v)environment.&lt;/p&gt;
&lt;p&gt;At some point soon I'll be adding &lt;code&gt;port79&lt;/code&gt; as a dependency of Rogallo and
adding &lt;code&gt;finger&lt;/code&gt; as a "native protocol".&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/07/21/port79-a-finger-protocol-library-for-python.html"/>
    <category term="Python"/>
    <category term="Coding"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <category term="Rogallo"/>
    <category term="finger"/>
    <category term="port79"/>
    <category term="smolweb"/>
    <published>2026-07-21T16:19:20+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/07/18/wasat-v0-8-0.html</id>
    <title>Wasat v0.8.0</title>
    <updated>2026-07-18T10:23:11+01:00</updated>
    <content type="html">&lt;p&gt;&lt;a href="https://wasat.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Wasat&lt;/a&gt;
&lt;a href="https://wasat.davep.dev/changelog/#v080" rel="noopener noreferrer" target="_blank"&gt;v0.8.0&lt;/a&gt; is now available. This
provides a small update to the &lt;code&gt;GeminiURI&lt;/code&gt; class, adding three utility
properties for extra URI manipulation.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;without_query&lt;/code&gt; - gives the URI minus any attached query&lt;/li&gt;
&lt;li&gt;&lt;code&gt;parent&lt;/code&gt; - gives the parent path of the current URI (also minus the query)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;root&lt;/code&gt; - gives the root path for the current URI (also minus the query)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In each case, the result is a fresh &lt;code&gt;GeminiURI&lt;/code&gt; instance.&lt;/p&gt;
&lt;p&gt;These are added because I'm aiming to add "go to parent" and "go to root"
commands to &lt;a href="https://rogallo.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Rogallo&lt;/a&gt;.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/07/18/wasat-v0-8-0.html"/>
    <category term="Python"/>
    <category term="Coding"/>
    <category term="Gemini Protocol"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <category term="smolweb"/>
    <category term="wasat"/>
    <published>2026-07-18T10:23:11+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/07/16/wasat-v0-7-0.html</id>
    <title>Wasat v0.7.0</title>
    <updated>2026-07-16T17:07:52+01:00</updated>
    <content type="html">&lt;p&gt;The &lt;a href="https://geminiprotocol.net/docs/protocol-specification.gmi" rel="noopener noreferrer" target="_blank"&gt;Gemini
Protocol&lt;/a&gt; makes
it clear that &lt;a href="https://geminiprotocol.net/docs/protocol-specification.gmi#requests" rel="noopener noreferrer" target="_blank"&gt;the maximum length of a URI, when making a request, is 1024
bytes&lt;/a&gt;.
This has implications for how large a user's input can be when responding to
a &lt;code&gt;1x&lt;/code&gt; response. Because of this, I'd like to update
&lt;a href="https://blog.davep.org/tag/rogallo/"&gt;Rogallo&lt;/a&gt; so that it lets the user know how much space they
have left as they type in their input.&lt;/p&gt;
&lt;p&gt;With this in mind I've released
&lt;a href="https://wasat.davep.dev/changelog/#v070" rel="noopener noreferrer" target="_blank"&gt;v0.7.0&lt;/a&gt; of
&lt;a href="https://wasat.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Wasat&lt;/a&gt;. This adds the following to &lt;code&gt;GeminiURI&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GeminiURI.MAXIMUM_LENGTH&lt;/code&gt; -- a constant for the maximum length of a
  Gemini URI (as mentioned above: 1024).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;len()&lt;/code&gt; support -- if you ask for the &lt;code&gt;len&lt;/code&gt; of an instance of &lt;code&gt;GeminiURI&lt;/code&gt;
  it will return the length of the full URI.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GeminiURI.bytes_left&lt;/code&gt; is a property that tells you how many bytes are
  left until the limit, given the current URI.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GeminiURI.too_long&lt;/code&gt; is a boolean property that flags if the current URI
  is too long to send to a Gemini capsule.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This should give me all I need to add some guardrails to the user input
dialog in Rogallo.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/07/16/wasat-v0-7-0.html"/>
    <category term="Python"/>
    <category term="Coding"/>
    <category term="Gemini Protocol"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <category term="smolweb"/>
    <category term="wasat"/>
    <published>2026-07-16T17:07:52+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/07/16/gemtext-v1-0-0.html</id>
    <title>Gemtext v1.0.0</title>
    <updated>2026-07-16T08:58:50+01:00</updated>
    <content type="html">&lt;p&gt;I've bumped &lt;a href="https://gemtext.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Gemtext&lt;/a&gt; to
&lt;a href="https://gemtext.davep.dev/changelog/#v100" rel="noopener noreferrer" target="_blank"&gt;v1.0.0&lt;/a&gt;. I've gone from "this is
test code" to "this is stable code" mostly because the library is so simple
and I'm unlikely to add any radically new features to it&lt;sup id="fnref:409-1"&gt;&lt;a class="footnote-ref" href="#fn:409-1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;This bump also has one small addition: I've added &lt;a href="https://gemtext.davep.dev/gemtext/#gemtext.PreFormatted.alt_text" rel="noopener noreferrer" target="_blank"&gt;an &lt;code&gt;alt_text&lt;/code&gt;
property&lt;/a&gt;
to &lt;a href="https://gemtext.davep.dev/gemtext/#gemtext.PreFormatted" rel="noopener noreferrer" target="_blank"&gt;the &lt;code&gt;PreFormatted&lt;/code&gt;
class&lt;/a&gt;. This exists
to capture and make available any text that comes after the pre-formatted
text marker. For example, this text:&lt;/p&gt;
&lt;div class="highlight" data-lang="gemtext"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;```python
print(&amp;quot;Hello, World!&amp;quot;)
```
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;will now result in:&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="n"&gt;PreFormatted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;print(&amp;quot;Hello, World!&amp;quot;)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alt_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;python&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If/when I &lt;a href="https://github.com/davep/rogallo/issues/151" rel="noopener noreferrer" target="_blank"&gt;add syntax highlighting of pre-formatted text blocks to
Rogallo&lt;/a&gt;, this new property
will make that possible.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:409-1"&gt;
&lt;p&gt;I was thinking about adding a Gemtext builder class at some point,
but I can't see a use for it any time soon. Perhaps v2.0.0.&amp;#160;&lt;a class="footnote-backref" href="#fnref:409-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;</content>
    <link href="https://blog.davep.org/2026/07/16/gemtext-v1-0-0.html"/>
    <category term="Python"/>
    <category term="Coding"/>
    <category term="Gemini Protocol"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <category term="gemtext"/>
    <category term="smolweb"/>
    <published>2026-07-16T08:58:50+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/07/14/wasat-v0-6-0.html</id>
    <title>Wasat v0.6.0</title>
    <updated>2026-07-14T12:14:35+01:00</updated>
    <content type="html">&lt;p&gt;&lt;a href="https://wasat.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Wasat&lt;/a&gt;
&lt;a href="https://wasat.davep.dev/changelog/#v060" rel="noopener noreferrer" target="_blank"&gt;v0.6.0&lt;/a&gt; is now available. This is
another quick update that fixes a small typing issue and also adds a handy
new method I've been meaning to add to
&lt;a href="https://wasat.davep.dev/library/uri/#wasat.uri.GeminiURI" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;GeminiURI&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The typing issue is a simple enough one. The &lt;code&gt;__init__&lt;/code&gt; method for
&lt;code&gt;GeminiURI&lt;/code&gt; can take either a string or another &lt;code&gt;GeminiURI&lt;/code&gt; as its argument.
However, the type was actually specified as &lt;code&gt;str | Self&lt;/code&gt;. In
&lt;a href="https://rogallo.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Rogallo&lt;/a&gt;, I want to have a sub-class of
&lt;code&gt;GeminiURI&lt;/code&gt; for one particular purpose, which will be passed an instance of
&lt;code&gt;GeminiURI&lt;/code&gt;. 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;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;KnownHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GeminiURI&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;A known host.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;From this, I want to be able to do:&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;KnownHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&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;host&lt;/span&gt; &lt;span class="ow"&gt;in&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;known_hosts&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;where &lt;code&gt;known_hosts&lt;/code&gt; is typed as &lt;code&gt;list[GeminiURI]&lt;/code&gt;. At this point, the type
checker complains:&lt;/p&gt;
&lt;div class="highlight" data-lang="text"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Argument 1 to &amp;quot;KnownHost&amp;quot; has incompatible type &amp;quot;GeminiURI&amp;quot;; expected &amp;quot;str | KnownHost&amp;quot;  [arg-type]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The error is correct, because the use of &lt;code&gt;Self&lt;/code&gt; is saying "this needs to be
an instance of my class". There's no reason why it needs to work this way,
so I've relaxed the type to &lt;code&gt;str | GeminiURI&lt;/code&gt;. In my example above,
&lt;code&gt;KnownHost&lt;/code&gt; is a subclass of &lt;code&gt;GeminiURI&lt;/code&gt;, so the type checker will be happy
again.&lt;/p&gt;
&lt;p&gt;The new method I've added is &lt;code&gt;GeminiURI.with_default_scheme&lt;/code&gt;. This is a
class method that acts as a more relaxed "constructor" for a &lt;code&gt;GeminiURI&lt;/code&gt;.
Again, in Rogallo, there are a few places where I'm taking some input,
assuming it's going to be a &lt;code&gt;gemini://&lt;/code&gt; URI, checking if it's missing the
scheme prefix, and then prefixing the string with &lt;code&gt;gemini://&lt;/code&gt; before
creating a &lt;code&gt;GeminiURI&lt;/code&gt;&lt;sup id="fnref:407-1"&gt;&lt;a class="footnote-ref" href="#fn:407-1"&gt;1&lt;/a&gt;&lt;/sup&gt;. This means that Rogallo contains a few instances
of this sort of code:&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;if&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# ...we should treat some text as a URI but it isn&amp;#39;t prefixed with &amp;quot;gemini://&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GeminiURI&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;GEMINI_PREFIX&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It's a small difference, but from now on I'll be able to:&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="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GeminiURI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;with_default_scheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This removes the need to check if there's a scheme already &lt;em&gt;and&lt;/em&gt; it saves me
having to import &lt;code&gt;GEMINI_PREFIX&lt;/code&gt;, etc.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:407-1"&gt;
&lt;p&gt;&lt;code&gt;GeminiURI&lt;/code&gt; will deliberately raise an exception if the scheme isn't &lt;code&gt;gemini&lt;/code&gt;.&amp;#160;&lt;a class="footnote-backref" href="#fnref:407-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;</content>
    <link href="https://blog.davep.org/2026/07/14/wasat-v0-6-0.html"/>
    <category term="Python"/>
    <category term="Coding"/>
    <category term="Gemini Protocol"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <category term="smolweb"/>
    <category term="wasat"/>
    <published>2026-07-14T12:14:35+01:00</published>
  </entry>
</feed>
