Posts tagged with "PyPI"

Visual Selection

3 min read; 11 GFI

Over the last few weeks I've had a couple of sessions of working on a library to wrap Plotext -- a popular terminal-based plotting library for Python -- so that it can easily be used in Textual apps; textual-plotext is the result.

I feel it's come together pretty well

But... I've been itching to find a reason to use it in a project of my own.

Meanwhile...

Back in the mid-2000s, when phpBB systems were still the fashion, I used to hang out on a site that was chiefly aimed at the atheist and secular humanist crowd. We'd get a good number of drive-by YEC types who'd want to argue (sorry... debate) and often talk nonsense about biology and the like.

Now, I'm no biologist, I'm no scientist, I'm just a hacker who likes to write code for fun and profit; so any time there was a chance to write some code to help illustrate an idea I'd jump at the chance. I forget the detail now -- this was back in 2008; 15 years ago as of the time of writing -- but one time I remember a conversation was taking place where someone was just flat out claiming that "random mutation" can only cause "loss of information" and could never lead to a "desired result", or some such thing.

If you've ever had, read or watched those debates, you'll know the sort of thing I mean.

So that got me thinking back then, could I write something that could give a simple illustration of how this doesn't quite make sense?

So I had a little hacking session and came up with some Ruby code1 that did what I felt was the job. You'd give it a phrase you wanted it to generate (a stand-in for the current "fitness landscape", in effect), it would then generate a totally random string of that length, and then would set about mutating it, finding mutations that were "fitter" than others (a stand in for selection), breed the best two so far (randomly copy one chunk from another to create a child), then repeat over and over.

When I first wrote it I wasn't sure what to expect; would it ever finish given a reasonably large target string?

It did.

It was fun to code.

It got posted to the BB and of course wasn't in any way persuasive to them (honestly I never expected it would be). I seem to recall it being hand-waved away with calls of there obviously being an intelligent designer involved2.

Anyway, the "meanwhile..." in this: a few times this year I've thought it could be fun to rework this in Python (it's really not that complex after all; just a string-chopping loop really) and use Textual to put a fun UI on it.

So, that's what I did, complete with textual-plotext plot:

Visual Selection in action

While, 15 years on, this isn't going to convince anyone of the underlying point, I think it does serve a good educational purpose. It shows that you can create a fun UI for the terminal, with Textual, with not a lot of code. It also shows off how you can easily create dynamic plots. Plus -- and I think this might be the really important one -- it shows you can write "traditional" tight-loop code in a Textual application and still have a responsive UI; all thanks to the worker API.

The heart of the code for this application is this:

environment = Environment("This is the target string we want to create!")
while not environment.best_fit_found:
    environment.shit_happens()

Sure, there's some detail in the Environment class, but you get the idea: while we've not hit the target, let life find a way. A loop like that would totally bog down an application with a UI without some other work taking place. With Textual and workers the resulting method in the application, complete with code to send updates to the UI, really doesn't look much different:

@work(thread=True, exclusive=True)
def run_world(self, target: str) -> None:
    worker = get_current_worker()
    environment = Environment(target)
    iterations = 0
    self.post_message(self.WorldUpdate(environment, iterations, *environment.best))
    while not worker.is_cancelled and not environment.best_fit_found:
        environment.shit_happens()
        iterations += 1
        if (iterations % 1000) == 0 or environment.best_fit_found:
            self.post_message(
                self.WorldUpdate(environment, iterations, *environment.best)
            )
    if environment.best_fit_found:
        self.post_message(self.Finished(iterations))

I honestly think the worker API is one of the coolest things added to Textual and I so often see people have real "woah!" moments when they get to grips with it.

Anyway... I've covered science, religion, and how Ruby is better than Python, so I'm sure I've annoyed almost everyone. Job done I guess. ;-)

If you want to check out the app itself there's a GitHub repo and it can also be installed from PyPI using pipx.

Expect it to be my tinker project of choice for a wee while; there's a couple of other things I'd like to add to it.


  1. Possibly unpopular opinion with some folk who will read this, but I've long been a fan of Ruby as a language and actually generally prefer it to Python. 

  2. Me, the coder. While utterly missing the point of a simple illustration, while apparently not understanding the concept of an analogy, I guess at least they felt I was intelligent? 

astare v0.8.0 released

1 min read; 10 GFI

textual-astare 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.

The app in action

I've just made a small update to it this evening after someone asked for a sensible change I've been meaning to do for a while. When I first read the request I was 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".

So anyway, yeah, v0.8.0 is out there and can be installed, with the main changes being:

  • Updated textual-fspicker
  • Updated textual
  • Made it so you can open a directory to browser from the command line.
  • Made opening the current working directory the default.
  • Tweaked the way dark/light mode get toggled so that it's now command-palette-friendly.

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.

Mandelbrot Commands

1 min read; 13 GFI

I don't think I've mentioned it before on this blog, but some time back I decided it would be fun to use Textual to write a Mandelbrot explorer (simple Mandelbrot explorers have been another one of my favourite known problem to try an unknown thing problems). Doing it in the terminal seemed like a fun little hack. I started off with creating textual-canvas and then built textual-mandelbrot on top of that.

Not too long back I added a "command palette" to Textual (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 textual-mandelbrot.

Mandelbrot commands in action

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.

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.

What I really liked though was how easy this was to do. The code to make the commands available 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.

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 CLIM -- I remember really liking how you could create self-documenting and self-completing commands in that).

All in good time...

Textual Query Sandbox Update

1 min read; 12 GFI

Since quickly hacking together textual-query-sandbox a few days back, 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.

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 TabbedContent. So as of now the tool still has the original playground which had an emphasis on nested containers:

Playground 1

There's now a playground with an emphasis on selecting widgets within containers1:

Playground 2

There's also now a playground that has an emphasis on pulling out widgets based on ID and classes:

Playground 3

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.

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.

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 lot of space2 available to show the playground and the results. Finding a good balance is an interesting problem.

For a number of reasons this is turning into a really enjoyable tinker project.


  1. This is, of course, slightly nonsensical wording. Containers are widgets in Textual. Pretty much everything you see in your terminal is a widget, even a Screen is a widget. 

  2. 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. 

Textual Query Sandbox

2 min read; 16 GFI

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 has to be created there and then. Has to be!

This happened yesterday evening.

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 "list of stuff I should make" that I keep in Apple Reminders; not sure why really. But then yesterday evening a question cropped up on the Textual Discord server that related to the subject and I was reminded of it.

The subject being: Textual DOM queries. 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.

So textual-query-sandbox was born!

Textual Query Sandbox

In this very first version (which was really 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 deployment to PyPI and writing the README) there's an Input, a display of a group of nested containers with different IDs and classes, and then a Pretty widget at the bottom to show the query result.

If you think this looks like it might be useful to you, it can be installed using either pip or (ideally) pipx:

pipx install textual-query-sandbox

and then you can run it with:

tqs

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.

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 TabbedContent 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.

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.

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.

Unbored v0.6.0

1 min read; 11 GFI

Late on last year I wrote about a bunch of new things that I'd added to PyPI, things mostly kicked off by an early dog-fooding session we had at textual HQ. Since then I've been slowly doing my best to keep the applications up to date with Textual.

Unbored

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.

Unbored, 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.

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.

While I was in there I also updated the application so that I dropped the nifty little slide-in error dialog I'd made, and instead embraced the new Textual notification system.

While the application itself is a bit silly, and likely of no real 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.

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 does break and they yell.

textual-canvas v0.2.0

1 min read; 12 GFI

Demo of textual-canvas

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 here anyway -- I have spoken lots about them over on Fosstodon). One such project is textual-canvas.

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.

I've just released a quick update after it was requested that I add a clear method to the Canvas widget; a request that makes perfect sense.

OIDIA

2 min read; 10 GFI

Another little thing that's up on PyPI now, which is the final bit of fallout from the Textual dogfooding sessions, is a little project I'm calling OIDIA.

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...

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.

No totals. No stats. No reminders and bugging. No judgement.

Here it is in action:

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.

On macOS and GNU/Linux streaks.json lives in ~/.local/share/oidia, on Windows it'll be in... I'm not sure off the top of my head actually; it'll be in whatever directory the handy xdg library has chosen. and because it's JSON that means that something like this:

OIDIA in action

ends up looking like this:

[
    {
        "title": "Hack some Python",
        "days": {
            "2022-12-02": 1,
            "2022-12-03": 1,
            "2022-12-04": 1,
            "2022-12-05": 1,
            "2022-12-06": 1,
            "2022-12-07": 1,
            "2022-12-08": 1,
            "2022-12-01": 1,
            "2022-11-30": 1,
            "2022-11-29": 1,
            "2022-11-28": 1
        }
    },
    {
        "title": "Brush my teeth",
        "days": {
            "2022-12-02": 2,
            "2022-12-03": 2,
            "2022-12-04": 2,
            "2022-12-05": 2,
            "2022-12-06": 2,
            "2022-12-07": 2,
            "2022-12-08": 1,
            "2022-12-01": 2,
            "2022-11-30": 2,
            "2022-11-29": 2,
            "2022-11-28": 2
        }
    },
    {
        "title": "Walk",
        "days": {
            "2022-12-02": 1,
            "2022-12-03": 1,
            "2022-12-04": 1,
            "2022-12-05": 1,
            "2022-12-06": 1,
            "2022-12-07": 1,
            "2022-12-08": 1,
            "2022-12-01": 1,
            "2022-11-30": 1,
            "2022-11-29": 1,
            "2022-11-28": 1
        }
    },
    {
        "title": "Run 5k",
        "days": {
            "2022-12-03": 2,
            "2022-12-05": 1,
            "2022-11-30": 1,
            "2022-11-28": 2
        }
    },
    {
        "title": "Run 10k",
        "days": {
            "2022-12-03": 1,
            "2022-11-28": 1
        }
    }
]

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.

If this seems like your thing (and I will be supporting it and onward developing it) you can find it over on PyPI, which means it can be installed with pip or the ever-handy pipx:

pipx install oidia

Be the Keymaster!

(Modified: 2026-04-28 10:21:31 UTC+01:00)
3 min read; 10 GFI

That didn't go to plan

So... yeah... the dogfooding... When I wrote my previous post I had wanted to try and do a post towards the end of each week, highlighting what I'd done on the "dogfooding" front. Life kinda had other plans. Not in a terrible way, but it turns out that getting both flu and Covid jabs (AKA "jags" as they tend to say in my adopted home) on the same day doesn't really agree with me too well.

I have been working, but there's been some odd moments in the past week and a bit and, last week, once I got to the end, I was glad for it to end. So no blog post happened.

Anyway...

What have I been up to?

While mostly sat feeling sorry for myself on my sofa, I have been coding. Rather than list all the different things here in detail, I'll quickly mention them with links to where to find them and play with them if you want:

FivePyFive

While my Textual 5x5 puzzle is one of the examples in the Textual repo, I wanted to make it more widely available so people can download it with pip or pipx. See over on PyPI and see if you can solve it. ;-)

textual-qrcode

I wanted to put together a very small example of how someone may put together a third party widget library, and in doing so selected what I thought was going to be a mostly-useless example: a wrapper around a text-based QR code generator website. Weirdly I've had a couple of people express a need for QR codes in the terminal since publishing that!

A Textual QR Code

PISpy

PISpy is a very simple terminal-based client for the PyPI API. Mostly it provides a hypertext interface to Python package details, letting you look up a package and then follow its dependency links. It's very simple at the moment, but I think more fun things can be done with this.

OIDIA

I'm a big fan of the use of streak-tracking in one form or another. Personally I use a streak-tracking app for keeping tabs of all sorts of good (and bad) habits, and as a heavy user of all things Apple I make a lot of use of the Fitness rings, etc. So I got to thinking it might be fun to do a really simple, no shaming, no counting, just recording, streak app for the Terminal. OIDIA is the result.

As of the time of writing I only finished the first version of this yesterday evening, so there are plenty of rough edges; but having got it to a point where it performed the basic tasks I wanted from it, that seemed like a good time to publish.

Expect to see this getting more updates and polish.

Wait, what about this Keymaster thing?

Ahh, yes, about that... So one of the handy things I'm finding about Textual is its key binding system. The more I build Textual apps, the more I appreciate the bindings, how they can be associated with specific widgets, the use of actions (which can be used from other places too), etc.

But... (there's always a "but" right -- I mean, there'd be no blog post to be had here otherwise).

The terminal doesn't have access to all the key combinations you may want to use, and also, because some keys can't necessarily be "typed", at least not easily (think about it: there's no F1 character, you have to type F1), many keys and key combinations need to be bound with specific names.

So there's two problems here: how do I discover what keys even turn up in my application, and when they do, what should I call them when I pass them to Binding?

That felt like a "well Dave just build an app for it!" problem. So I did:

If you're building apps with Textual and you want to discover what keys turn up from your terminal and are available to your application, you can:

pipx install textual-keys

and then just run textual-keys and start mashing the keyboard to find out.

There's a good chance that this app, or at least a version of it, will make it into Textual itself (very likely as one of the devtools). But for now it's just an easy install away.

I think there's a call to be made here too: have you built anything to help speed up how you work with Textual, or just make the development experience "just so"? If so, do let folk know, and come yell about it on the #show-and-tell channel in the Discord server.

ℹ️ Note

This personal development blog post was first hosted on the Textual devlog.

New Things On PyPI

(Modified: 2026-04-28 11:12:56 UTC+01:00)
4 min read; 9 GFI

An update

So, it's fast approaching 2 months now since I started the new thing 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 best 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.

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 a dog-fooding campaign we're on at the moment. 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.

gridinfo

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 slstats.el. It started out as a rather silly quick hack, designed to do something different with rich-pixels.

Here's the finished version (as of the time of writing) being put through its paces:

Download from here, or install and play with it with a quick pipx install gridinfo.

unbored

The next experiment with Textual was to write a terminal-based client for the Bored-API. 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!

And so on.

Here's a short video of it in action:

Download from here, or install and play with it with a quick pipx install unbored.

textual-qrcode

This one... this one I'm going to blame on the brain fog that followed flu and 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.

And... yeah, I don't know why, but I remembered qrencode.el and so textual-qrcode was born!

The most useless Textual widget yet

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!"

If you're one of those people... you'll find it here.

FivePyFive

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 a version in Lisp.

It's since gone on to become one of the example apps in Textual itself 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:

pipx install fivepyfive

and click away.

You can find it over here.

PISpy

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:

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.

See over here for the package, and you can install it with a:

pipx install pispy-client

and then just run pispy in the terminal.

Conclusion

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.