Posts tagged with "wasat"

Wasat v0.7.0

1 min read; 6 GFI

The Gemini Protocol makes it clear that the maximum length of a URI, when making a request, is 1024 bytes. This has implications for how large a user's input can be when responding to a 1x response. Because of this, I'd like to update Rogallo so that it lets the user know how much space they have left as they type in their input.

With this in mind I've released v0.7.0 of Wasat. This adds the following to GeminiURI:

  • GeminiURI.MAXIMUM_LENGTH -- a constant for the maximum length of a Gemini URI (as mentioned above: 1024).
  • len() support -- if you ask for the len of an instance of GeminiURI it will return the length of the full URI.
  • GeminiURI.bytes_left is a property that tells you how many bytes are left until the limit, given the current URI.
  • GeminiURI.too_long is a boolean property that flags if the current URI is too long to send to a Gemini capsule.

This should give me all I need to add some guardrails to the user input dialog in Rogallo.

Wasat v0.6.0

1 min read; 8 GFI

Wasat v0.6.0 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 GeminiURI.

The typing issue is a simple enough one. The __init__ method for GeminiURI can take either a string or another GeminiURI as its argument. However, the type was actually specified as str | Self. In Rogallo, I want to have a sub-class of GeminiURI for one particular purpose, which will be passed an instance of GeminiURI. Something like this:

class KnownHost(GeminiURI):
    """A known host."""

From this, I want to be able to do:

[KnownHost(host) for host in self.known_hosts]

where known_hosts is typed as list[GeminiURI]. At this point, the type checker complains:

Argument 1 to "KnownHost" has incompatible type "GeminiURI"; expected "str | KnownHost"  [arg-type]

The error is correct, because the use of Self 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 str | GeminiURI. In my example above, KnownHost is a subclass of GeminiURI, so the type checker will be happy again.

The new method I've added is GeminiURI.with_default_scheme. This is a class method that acts as a more relaxed "constructor" for a GeminiURI. Again, in Rogallo, there are a few places where I'm taking some input, assuming it's going to be a gemini:// URI, checking if it's missing the scheme prefix, and then prefixing the string with gemini:// before creating a GeminiURI1. This means that Rogallo contains a few instances of this sort of code:

if ...: # ...we should treat some text as a URI but it isn't prefixed with "gemini://"
    uri = GeminiURI(f"{GEMINI_PREFIX}{text}")

It's a small difference, but from now on I'll be able to:

uri = GeminiURI.with_default_scheme(text)

This removes the need to check if there's a scheme already and it saves me having to import GEMINI_PREFIX, etc.


  1. GeminiURI will deliberately raise an exception if the scheme isn't gemini

Wasat v0.5.0

1 min read; 12 GFI

Another quick update to Wasat, my Gemini Protocol client library for Python.

With v0.5.0 I've added a method for getting the list of currently-trusted hosts, and also added a public property to the client class for getting access to the trust store object.

Generally these shouldn't be required, shouldn't be something you'd normally want to work with; I've added them because I thought it might be another useful way of populating the suggested completions facility in the command line inside Rogallo. The idea being: if you're trying to remember the name of a capsule you've visited before, and it might have fallen out of the location history, its trust status might still be recorded so it can complete from there.

Wasat v0.4.0

2 min read; 10 GFI

By this point today I was hoping to have released a new version of Rogallo, complete with client certificate support. It is more or less all there and ready to go, but I ran into a small problem, something which confused me.

You see, according to the documentation for the Gemini Protocol, when there's a request by a capsule for a client certificate, that certificate should be scoped to the host, port and path and all paths below it. So that means that if example.com/foo causes a certificate to be requested, it's good for example.com/foo and example.com/foo/bar, but it isn't valid for example.com/other.

Makes sense.

The problem I ran into pretty quickly, with my implementation of this in Wasat and Rogallo, was this: I hit a site (a microblogging site of sorts) that had a joining page at example.com/join. Once you joined up and set your user name, you'd normally land at example.com/me. They're sibling paths and so should not use the same certificate. However, this was done via redirection, so I did some work to "clone" a certificate when there's a redirection.

But then it got more complicated. The site also lets you follow other people. This means that if you visit example.com/other-user you should also still have the client certificate in place so the capsule knows who you are (because client certificates are also, in effect, session cookies, as I understand it), so you can perform the follow. Again, this is a sibling path, and there's no redirection, so the certificate is no good because, at this point, it's scoped to example.com/join and example.com/me.

Meanwhile: testing this with Lagrange, it had no such problem whatsoever. How was it getting around this issue? Was I doing something wrong? Was I misreading the specification for the protocol? Was Lagrange being a bit more relaxed about its certificate scope?

After doing some digging, it would seem that it's the latter. It looks like it, and perhaps other clients, take a pragmatic approach to certificate scopes and generally scope them to the host and port alone, ignoring the path (in the case of Lagrange it seems to sort of actually divorce the certificates from the URIs anyway, treating certificates more as identities you can associate with any capsule, etc).

Given this, at least for now, I'm going to take that approach. Any time a certificate needs to be generated in Rogallo, I'm going to give the user the option (on by default) to scope the certificate to the whole host/port combination. Later on I might add the ability to fully manage certificates (right now that can be done by editing the certificates file in the data directory, but a proper UI for it would be nice).

Which brings me to this release of Wasat. v0.4.0 adds some extra functionality to GeminiURI that allows changing and removing individual parts of the URI. There's a new replace method that can be used to create a clone of a URI with various parts changed. Also, if you just want to change one specific part or simply prefer method chaining approaches, I've also added more with_ methods similar to the pre-existing with_query method, so now there's also with_host, with_port and with_path.

With these in place I can go back to Rogallo and carry on with the last bits of work I want to do on client certificate support, with Wasat making it easy for me to clone up and chop and change URIs to suit the scoping requirements.

Wasat v0.3.0

1 min read; 11 GFI

Wasat v0.3.0 is now available. This is a pretty small update, but one that's going to be useful in the next release of Rogallo. I've now got client certificate support up and going in the application, and I thought it might be useful to show the user if a given page is using a client certificate or not (and, perhaps, at some point, make the details of the certificate available as part of some page information dialog).

With this in mind, I've added client_cert_path and client_cert_used properties to the Response class. The former is the path to the certificate used (if one was used), the latter is a simple boolean flag to say if a certificate was used at all.

Of course, client_cert_path could be used for both purposes as it'll be None if one isn't used, but client_cert_used will read better in code if that's all that's needed.

Wasat v0.2.0

1 min read; 10 GFI

I've made a quick update to Wasat, my async Gemini Protocol client library for Python. Now that I'm at a point where I want to add client certificates to Rogallo, I'm essentially shaking down the support for this in Wasat.

One thing I wanted right away was a certificate that, in effect, never expires. So with this release of Wasat I've added an option where the days to expire can be set to None, which results in the expiration date being set to 9999-12-31.

Wasat v0.1.0

1 min read; 11 GFI

I've just released v0.1.0 of Wasat, my async Gemini Protocol client library for Python.

Changes in this release include:

  • Support for generating and storing client certificates to help when handling 6x responses. This is still experimental.
  • Updated the CLI to handle requests for input (handling 1x responses).
  • Added a uri property to the Response class, to expose the target URI reached from a request.
  • Added a history property to the Response class, to expose the redirect history if redirection took place.
  • Added a requested_uri property to the Response class, to expose the originally requested URI.
  • Updated the CLI so that, when in verbose mode, it prints all of the available redirection information.

Most of the changes here are in support of resolving an issue I found with Rogallo yesterday. With v0.1.0 available I should be able to update Rogallo with an easy fix.

So far, building this library, and the client application, is proving to be really interesting and educational. There's something fun about building a "web browser" of sorts, from the ground up. It really hits this point:

{Gemini might be of interest to you if you} Are a hobbyist programmer with a "do it yourself" attitude who enjoys building their own tools and getting real use out of them every day

from the Gemini protocol FAQ. For me, in "hobbyist programmer" mode, this is all kinds of fun.

Wasat - A Gemini protocol library for Python

2 min read; 10 GFI

I can't remember how and where I saw it, but just over a week ago I ran into Project Gemini. Somehow I've never read or seen anything about this before, which is pretty wild considering it's been on the go for around seven years now. As I read up on it I got more and more intrigued. I had the urge to do... something with it.

I think the thing I want to do, and I know I'm far from the first, is write a client for the terminal. I'm envisioning something very similar to Hike, but obviously only targeting the gemini protocol itself, and only handling and rendering gemtext.

I will, obviously, be looking to write this in Python, and of course, will be looking to use Textual, which means that it would be useful to have a Python client library for the protocol, and ideally a client library that is async. I did some searching, found client libraries, but none of them seemed to be async-first.

With this in mind, and to kick-start the project, last night I fired up Antigravity1 and got a library up and going. Wasat is the result. For the moment this should be considered alpha-status software (hence v0.0.1). I've done some very rudimentary testing and experimenting with it and, so far, so good. It's also proving to be a good tool with which to get to know the protocol. It also gives me another project to use to experiment with an agent (this being the first project I've started from scratch using Antigravity).

Over the next few days I'm going to toy with the library more, clean up the code, look for any issues, and then I'll start on the client application. That will be hand-built; no AI. I have some ideas of fun things I want to do, especially when it comes to handling gemtext.

It'll be nice to have a new pet project that's a hand-coded project. The first significant one since OldNews.

Getting back to Wasat itself: I believe it has everything necessary to allow for writing such a client (which, to be fair, isn't much -- that's kind of the point of the protocol). It also comes with a simple CLI built in, which can be run with python -m wasat (if it's just installed as a library) or with wasat (if installed globally, along with any command scripts). The command itself is just a simple download tool for a "page" in a "capsule". For example, I can grab the content of a test capsule I've created:

$ wasat gemini://davep.gemcities.com/
# Introduction
Hey! I'm Dave. Normally you'd find me at:
=> https://www.davep.org My web site
or
=> https://blog.davep.org My blog
amongst other places. But I discovered Gemini and I'm really curious about
the idea, so here I am giving GemCities a go to get to know things a little
better.

(with thanks to GemCities for providing a neat little service).

As for where this is all going: there's no direction, really. I've found a neat new thing that I didn't know about before, the idea sits well with me, and I want to explore it more. It also gives me an excuse to do a thing I really enjoy doing: writing terminal-based TUI applications for the sake of it, and especially writing one that works just how I want.


  1. No, not that one, the other one